diff --git a/.github/workflows/clang-linux-nix-check.yml b/.github/workflows/clang-linux-nix-check.yml index 6169665c66..bb5447ac98 100644 --- a/.github/workflows/clang-linux-nix-check.yml +++ b/.github/workflows/clang-linux-nix-check.yml @@ -6,7 +6,7 @@ on: jobs: build-and-test: name: "Build and test Linux with clang" - runs-on: [self-hosted, Linux, X64, aws_autoscaling] + runs-on: [self-hosted, Linux, X64] steps: # https://github.com/actions/checkout/issues/1552 - name: Clean up after previous checkout diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index fef9c74d56..0a81129908 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -16,12 +16,12 @@ concurrency: cancel-in-progress: true jobs: - test-linux-gcc: - name: Gcc release full Linux testing - uses: ./.github/workflows/gcc-linux-nix-check.yml - if: | - always() && !cancelled() - secrets: inherit + # test-linux-gcc: + # name: Gcc release full Linux testing + # uses: ./.github/workflows/gcc-linux-nix-check.yml + # if: | + # always() && !cancelled() + # secrets: inherit test-linux-clang: name: Clang release full Linux testing @@ -45,10 +45,10 @@ jobs: always() && !cancelled() secrets: inherit - build-linux-proof-producer-deb-package: - name: Build and upload deb package - uses: ./.github/workflows/deb-package-proof-producer-bundler.yaml - if: | - always() && !cancelled() - # TODO add if it's a merge to master - secrets: inherit + # build-linux-proof-producer-deb-package: + # name: Build and upload deb package + # uses: ./.github/workflows/deb-package-proof-producer-bundler.yaml + # if: | + # always() && !cancelled() + # # TODO add if it's a merge to master + # secrets: inherit diff --git a/.github/workflows/verify-hardhat-proofs.yml b/.github/workflows/verify-hardhat-proofs.yml index 3e0507f5c2..78d3071bdf 100644 --- a/.github/workflows/verify-hardhat-proofs.yml +++ b/.github/workflows/verify-hardhat-proofs.yml @@ -6,7 +6,7 @@ on: jobs: build-and-test: name: "Verify hardhat proofs" - runs-on: [self-hosted, Linux, X64, aws_autoscaling] + runs-on: [self-hosted, Linux, X64] steps: # https://github.com/actions/checkout/issues/1552 - name: Clean up after previous checkout diff --git a/crypto3/libs/algebra/include/nil/crypto3/algebra/matrix/dmatrix.hpp b/crypto3/libs/algebra/include/nil/crypto3/algebra/matrix/dmatrix.hpp new file mode 100644 index 0000000000..fa85e93b1b --- /dev/null +++ b/crypto3/libs/algebra/include/nil/crypto3/algebra/matrix/dmatrix.hpp @@ -0,0 +1,180 @@ + +//---------------------------------------------------------------------------// +// Copyright (c) 2025 Elena Tatuzova +// +// 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 + +/** Dynamic size matrix class */ + +namespace nil::crypto3::algebra { + /** @brief A container representing a matrix + * @tparam T scalar type to contain + * + * `dmatrix` is a container representing a matrix. + */ + template + class dmatrix: public dvector> { + public: + using value_type = T; + using size_type = std::size_t; + using row_type = dvector; + using data_type = dvector>; + + size_type column_size; ///< Number of rows + size_type row_size; ///< Number of columns + + // Constructor + dmatrix(size_type N, size_type M) : column_size(N), row_size(M), dvector>(N, dvector(M)) {} + + // Constructor with initialization + dmatrix(std::size_t N, std::size_t M, const data_type &init_data) + : column_size(N), row_size(M), dvector>(init_data) {} + + + dvector row(std::size_t i) const { + dvector result = (*this)[i]; + return result; + } + + dvector column(std::size_t j) const{ + dvector result(column_size); + for (std::size_t i = 0; i < column_size; ++i) { + result[i] = (*this)[i][j]; + } + return result; + } + + dmatrix operator+(const dmatrix &other) const { + assert (column_size == other.column_size && row_size == other.row_size); + dmatrix result(column_size, row_size, dvector>::operator+(other)); + return result; + } + + dmatrix operator-(const dmatrix &other) const { + assert (column_size == other.column_size && row_size == other.row_size); + dmatrix result(column_size, row_size, dvector>::operator-(other)); + return result; + } + + dmatrix operator*(const dmatrix &other) const { + assert (row_size == other.column_size); + dmatrix result(column_size, other.row_size); + for (std::size_t i = 0; i < column_size; ++i) { + for (std::size_t j = 0; j < other.row_size; ++j) { + result[i][j] = 0; + for (std::size_t k = 0; k < row_size; ++k) { + result[i][j] += (*this)[i][k] * other[k][j]; + } + } + } + return result; + } + + T determinant() const { + assert (column_size == row_size); + T det = 1; + if (column_size == 0) return 1; + T sign = 1; // Check! + + dvector> tmp = *this; // Make a copy to perform row operations + for( std::size_t i = 0; i < column_size; i++ ){ + // Find pivot + std::size_t pivot = i; + while( pivot < column_size && tmp[pivot][i] == 0 ) pivot++; + if( pivot == column_size ) return 0; // Singular matrix + + if( pivot != i ){ + std::swap( tmp[i], tmp[pivot] ); + sign = -sign; + } + + det *= tmp[i][i]; + // Eliminate below + for( std::size_t j = i + 1; j < column_size; j++ ){ + T factor = tmp[j][i] / tmp[i][i]; + for( std::size_t k = i; k < row_size; k++ ){ + tmp[j][k] -= factor * tmp[i][k]; + } + } + + if (sign != 1) det = -det; + } + return det; + } + + std::size_t rank() const { + dvector> tmp = *this; // Make a copy to perform row operations + std::size_t rank = 0; + std::size_t min_size = std::min(column_size, row_size); + + for (std::size_t i = 0; i < min_size; ++i) { + // Find pivot + std::size_t pivot = i; + while (pivot < column_size && tmp[pivot][i] == 0) pivot++; + if (pivot == column_size) continue; // No pivot in this column + + if (pivot != i) { + std::swap(tmp[i], tmp[pivot]); + } + + // Eliminate below + for (std::size_t j = i + 1; j < column_size; ++j) { + T factor = tmp[j][i] / tmp[i][i]; + for (std::size_t k = i; k < row_size; ++k) { + tmp[j][k] -= factor * tmp[i][k]; + } + } + rank++; + } + return rank; + } + }; + + template + dmatrix identity_dmatrix(std::size_t N) { + dmatrix result(N, N); + for (std::size_t i = 0; i < N; ++i) { + result[i][i] = 1; + } + return result; + } + + template + dmatrix identity_dmatrix(std::size_t N, std::size_t M) { + dmatrix result(N, M); + std::size_t min_size = std::min(N, M); + for (std::size_t i = 0; i < min_size; ++i) { + result[i][i] = 1; + } + return result; + } +} // namespace nil::crypto3::algebra \ No newline at end of file diff --git a/crypto3/libs/algebra/include/nil/crypto3/algebra/vector/dvector.hpp b/crypto3/libs/algebra/include/nil/crypto3/algebra/vector/dvector.hpp new file mode 100644 index 0000000000..ba810cb25b --- /dev/null +++ b/crypto3/libs/algebra/include/nil/crypto3/algebra/vector/dvector.hpp @@ -0,0 +1,94 @@ + +//---------------------------------------------------------------------------// +// Copyright (c) 2025 Elena Tatuzova +// +// 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 + +/** Dynamic size vector class */ + +namespace nil::crypto3::algebra { + /** @brief A container representing a matrix + * @tparam T scalar type to contain + * + * `matrix` is a container representing a matrix. + * It is an aggregate type containing a single member array of type + * `T[N][M]` which can be initialized with aggregate initialization. + */ + template + class dvector :public std::vector { + public: + using value_type = T; + using size_type = std::size_t; + + // Constructor + dvector() : std::vector() {} + dvector(size_type N) : std::vector(N) {} + dvector(size_type N, const T &value) : std::vector(N, value) {} + dvector(std::initializer_list init) : std::vector(init) {} + + // Addition operator + dvector operator+(const dvector &other) const { + assert (this->size() == other.size()); + dvector result(this->size()); + std::transform( + this->begin(), this->end(), + other.begin(), + result.begin(), + [](const T &a, const T &b) { return a + b; } + ); + return result; + } + + // Subtraction operator + dvector operator-(const dvector &other) const { + assert (this->size() == other.size()); + dvector result(this->size()); + std::transform( + this->begin(), this->end(), + other.begin(), + result.begin(), + [](const T &a, const T &b) { return a - b; } + ); + return result; + } + + dvector operator*(const T &scalar) const { + dvector result(this->size()); + std::transform( + this->begin(), this->end(), + result.begin(), + [scalar](const T &a) { return a * scalar; } + ); + return result; + } + }; +} // namespace nil::crypto3::algebra \ No newline at end of file diff --git a/crypto3/libs/algebra/test/CMakeLists.txt b/crypto3/libs/algebra/test/CMakeLists.txt index 1fceeefd07..91b129a408 100644 --- a/crypto3/libs/algebra/test/CMakeLists.txt +++ b/crypto3/libs/algebra/test/CMakeLists.txt @@ -48,20 +48,18 @@ macro(define_compile_time_algebra_test name) endmacro() set(RUNTIME_TESTS_NAMES - "curves" - "short_weierstrass_coordinates" - "curves_static" - "fields" - "fields_static" - "pairing" - "type_traits" - "multiexp" + "curves" + "short_weierstrass_coordinates" + "curves_static" + "fields" + "fields_static" + "pairing" + "type_traits" + "multiexp" + "matrix" + "vector" ) -set(COMPILE_TIME_TESTS_NAMES - "matrix" - "vector") - foreach(TEST_NAME ${RUNTIME_TESTS_NAMES}) define_runtime_algebra_test(${TEST_NAME}) endforeach() diff --git a/crypto3/libs/algebra/test/matrix.cpp b/crypto3/libs/algebra/test/matrix.cpp index 4c11f71f25..a77a924a22 100644 --- a/crypto3/libs/algebra/test/matrix.cpp +++ b/crypto3/libs/algebra/test/matrix.cpp @@ -23,13 +23,14 @@ // SOFTWARE. //---------------------------------------------------------------------------// -#define BOOST_TEST_MODULE constexpr_matrix_test +#define BOOST_TEST_MODULE matrix_test #include #include #include #include +#include #include #include #include @@ -89,3 +90,146 @@ static_assert(submat<2, 2>(m1, 1, 1) == matrix {{{5, 6}, {8, 9}}}, static_assert(rref(m1) == matrix {{{1, 0, -1}, {0, 1, 2}, {0, 0, 0}}}, "rref"); static_assert(rank(m1) == 2, "rank"); + +BOOST_AUTO_TEST_SUITE(matrix_test) + using matrix_2_2 = matrix; + using matrix_2_2 = matrix; +BOOST_AUTO_TEST_CASE(equality){ + matrix_2_2 a = {{{1, 2}, {3, 4}}}; + matrix_2_2 b = {{{1, 2}, {3, 4}}}; + matrix_2_2 c = {{{5, 6}, {7, 8}}}; + BOOST_CHECK(a == b); + BOOST_CHECK(a != c); +} +BOOST_AUTO_TEST_CASE(addition){ + + matrix_2_2 a = {{{1, 2}, {3, 4}}}; + matrix_2_2 b = {{{5, 6}, {7, 8}}}; + matrix_2_2 c = a + b; + matrix_2_2 result = {{{6, 8}, {10, 12}}}; + BOOST_CHECK(c == result); + BOOST_CHECK(c == matrix_2_2({{{6, 8}, {10, 12}}})); +} +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(dmatrix_test) +using dynamic_matrix = dmatrix; + +BOOST_AUTO_TEST_CASE(construction){ + dynamic_matrix dm(3, 4); + BOOST_CHECK(dm.column_size == 3); + BOOST_CHECK(dm.row_size == 4); + + dynamic_matrix dm_init(2, 3, {{ {1, 2, 3}, {4, 5, 6} }}); + BOOST_CHECK(dm_init.column_size == 2); + BOOST_CHECK(dm_init.row_size == 3); + BOOST_CHECK(dm_init[0][0] == 1); + BOOST_CHECK(dm_init[0][1] == 2); + BOOST_CHECK(dm_init[0][2] == 3); + BOOST_CHECK(dm_init[1][0] == 4); + BOOST_CHECK(dm_init[1][1] == 5); + BOOST_CHECK(dm_init[1][2] == 6); + + auto row = dm_init.row(1); + BOOST_CHECK(row == dynamic_matrix::row_type({4, 5, 6})); + + auto column = dm_init.column(2); + BOOST_CHECK(column == dynamic_matrix::row_type({3, 6})); +} + +BOOST_AUTO_TEST_CASE(equality){ + dynamic_matrix a(2, 2, {{ {1, 2}, {3, 4} }}); + dynamic_matrix b(2, 2, {{ {1, 2}, {3, 4} }}); + dynamic_matrix c(2, 2, {{ {5, 6}, {7, 8} }}); + BOOST_CHECK(a == b); + BOOST_CHECK(a != c); +} + +BOOST_AUTO_TEST_CASE(addition){ + dynamic_matrix a(2, 3, {{ {1, 2, 3}, {4, 5, 6} }}); + dynamic_matrix b(2, 3, {{ {5, 6, 7}, {8, 9, 10} }}); + dynamic_matrix c = a + b; + dynamic_matrix result(2, 3, {{ {6, 8, 10}, {12, 14, 16} }}); + BOOST_CHECK(c == result); +} + +BOOST_AUTO_TEST_CASE(subtraction){ + dynamic_matrix a(3, 2, {{ {5, 6}, {7, 8}, {9, 10} }}); + dynamic_matrix b(3, 2, {{ {1, 2}, {3, 4}, {5, 6} }}); + dynamic_matrix c = a - b; + dynamic_matrix result(3, 2, {{ {4, 4}, {4, 4}, {4, 4} }}); + BOOST_CHECK(c == result); +} + +BOOST_AUTO_TEST_CASE(multiplication){ + dynamic_matrix a(2, 3, {{ {1, 2, 3}, {4, 5, 6} }}); + dynamic_matrix b(3, 4, {{ {7, 8, 9, 10}, {11, 12, 13, 14}, {15, 16, 17, 18} }}); + dynamic_matrix c = a * b; + dynamic_matrix result(2, 4, {{ {74, 80, 86, 92}, {173, 188, 203, 218} }}); + BOOST_CHECK(c == result); + + dynamic_matrix d(3, 3, {{ {1, 2, 3}, {0, 1, 4}, {5, 6, 0} }}); + dynamic_matrix e = identity_dmatrix(3); + dynamic_matrix f = d * e; + BOOST_CHECK(f == d); +} + +BOOST_AUTO_TEST_CASE(determinant){ + dynamic_matrix a(2, 2, {{ {4, 6}, {3, 8} }}); + value det = a.determinant(); + BOOST_CHECK_EQUAL(det, 14); + + // Test determinant of 3x3 matrix + dynamic_matrix b(3, 3, {{ {6, 1, 1}, {4, -2, 5}, {2, 8, 7} }}); + value det_b = b.determinant(); + BOOST_CHECK_EQUAL(det_b, -306); + + // Test zero determinant + dynamic_matrix c(2, 2, {{ {1, 2}, {2, 4} }}); + value det_c = c.determinant(); + BOOST_CHECK_EQUAL(det_c, 0); + + dynamic_matrix d(1, 1, {{ {5} }}); + value det_d = d.determinant(); + BOOST_CHECK_EQUAL(det_d, 5); + + dynamic_matrix e(0, 0); + value det_e = e.determinant(); + BOOST_CHECK_EQUAL(det_e, 1); + + dynamic_matrix id = identity_dmatrix(5); + value det_id = id.determinant(); + BOOST_CHECK_EQUAL(det_id, 1); +} + +BOOST_AUTO_TEST_CASE(rank){ + dynamic_matrix a(3, 3, {{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }}); + std::size_t rank_a = a.rank(); + BOOST_CHECK_EQUAL(rank_a, 2); + + dynamic_matrix b(2, 3, {{ {1, 2, 3}, {4, 5, 6} }}); + std::size_t rank_b = b.rank(); + BOOST_CHECK_EQUAL(rank_b, 2); + + dynamic_matrix c(3, 2, {{ {1, 2}, {2, 4}, {3, 6} }}); + std::size_t rank_c = c.rank(); + BOOST_CHECK_EQUAL(rank_c, 1); + + dynamic_matrix d(2, 2, {{ {1, 0}, {0, 1} }}); + std::size_t rank_d = d.rank(); + BOOST_CHECK_EQUAL(rank_d, 2); + + dynamic_matrix e(0, 0); + std::size_t rank_e = e.rank(); + BOOST_CHECK_EQUAL(rank_e, 0); + + dynamic_matrix f(3, 3, {{ {0, 0, 0}, {0, 0, 0}, {0, 0, 0} }}); + std::size_t rank_f = f.rank(); + BOOST_CHECK_EQUAL(rank_f, 0); + + dynamic_matrix id = identity_dmatrix(10); + std::size_t rank_id = id.rank(); + BOOST_CHECK_EQUAL(rank_id, 10); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/crypto3/libs/algebra/test/vector.cpp b/crypto3/libs/algebra/test/vector.cpp index 529c66be3e..e269d08852 100644 --- a/crypto3/libs/algebra/test/vector.cpp +++ b/crypto3/libs/algebra/test/vector.cpp @@ -1,6 +1,7 @@ //---------------------------------------------------------------------------// // Copyright (c) 2020-2021 Mikhail Komarov // Copyright (c) 2020-2021 Nikita Kaskov +// Copyright (c) 2025 Elena Tatuzova // // MIT License // @@ -30,6 +31,7 @@ #include #include +#include #include #include #include @@ -68,3 +70,35 @@ static_assert(generate<4>([](auto i) { return value(i * i); }) == vector(vector {1, 2, 3, 4}), "slice-no offset"); static_assert(vector {2, 3, 4} == slice<3>(vector {1, 2, 3, 4}, 1), "slice with offset"); + + +BOOST_AUTO_TEST_SUITE(dvector_test) + using dynamic_vector = dvector; + +BOOST_AUTO_TEST_CASE(addition){ + dynamic_vector a = {1, 2, 3}; + dynamic_vector b = {4, 5, 6}; + dynamic_vector c = a + b; + dynamic_vector result = {5, 7, 9}; + BOOST_CHECK(c == result); + BOOST_CHECK(c == dynamic_vector({5, 7, 9})); +} + +BOOST_AUTO_TEST_CASE(subtraction){ + dynamic_vector a = {7, 7, 6}; + dynamic_vector b = {1, 2, 3}; + dynamic_vector c = a - b; + dynamic_vector result = {6, 5, 3}; + BOOST_CHECK(c == result); + BOOST_CHECK(c == dynamic_vector({6, 5, 3})); +} + +BOOST_AUTO_TEST_CASE(multiplication){ + dynamic_vector a = {1, 2, 3}; + value scalar = 3; + dynamic_vector c = a * scalar; + dynamic_vector result = {3, 6, 9}; + BOOST_CHECK(c == result); + BOOST_CHECK(c == dynamic_vector({3, 6, 9})); +} +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/crypto3/libs/benchmark_tools/include/nil/crypto3/bench/scoped_profiler.hpp b/crypto3/libs/benchmark_tools/include/nil/crypto3/bench/scoped_profiler.hpp index b4067ef987..8c6529b2e7 100644 --- a/crypto3/libs/benchmark_tools/include/nil/crypto3/bench/scoped_profiler.hpp +++ b/crypto3/libs/benchmark_tools/include/nil/crypto3/bench/scoped_profiler.hpp @@ -39,6 +39,7 @@ #include #include #include +#include namespace nil::crypto3::bench::detail { template diff --git a/crypto3/libs/blueprint/include/nil/blueprint/lookup_library.hpp b/crypto3/libs/blueprint/include/nil/blueprint/lookup_library.hpp index bd406c52e8..961920a6e7 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/lookup_library.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/lookup_library.hpp @@ -86,55 +86,21 @@ namespace nil { virtual std::size_t get_rows_number(){ return 256; } }; - class zkevm_opcode_table: public lookup_table_definition{ + class opcode_push_size_table: public lookup_table_definition{ public: - static constexpr std::size_t opcodes_num = 149; - - zkevm_opcode_table(): lookup_table_definition("zkevm_opcodes"){ - this->subtables["full"] = {{0, 1, 2}, 0, opcodes_num}; - this->subtables["opcodes_only"] = {{0}, 0, opcodes_num}; + opcode_push_size_table(): lookup_table_definition("opcode_push_size") { + this->subtables["full"] = {{0, 1}, 0, 256}; } - virtual void generate(){ - // opcodes - this->_table.push_back({ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, //12 - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, //14 - 0x20, //1 - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, //16 - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, //11 - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, //16 - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, //16 - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, //16 - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, //16 - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, //16 - 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, //5 - 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xfa, 0xfd, 0xfe, 0xff //10 - }); - // push_size - this->_table.push_back({ - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //12 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //14 - 0x0, //1 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //16 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //11 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //16 - 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, //16 - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, //16 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //16 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, //16 - 0x0, 0x0, 0x0, 0x0, 0x0, //5 - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 //10 - }); - this->_table.push_back({}); - for( std::size_t i = 0; i < opcodes_num; i++) this->_table[2].push_back(1); + virtual void generate(){ + this->_table.resize(2); + for (size_t i = 0; i < 256; ++i) this->_table[0].push_back(i); - // unselected rows virtualization - this->_table[0].push_back(0); - this->_table[1].push_back(0); - this->_table[2].push_back(0); + this->_table[1].resize(256); + for (size_t i = 1; i <= 32; ++i) this->_table[1][0x5f + i] = i; } - virtual std::size_t get_columns_number(){ return 1; } + + virtual std::size_t get_columns_number(){ return 2; } virtual std::size_t get_rows_number(){ return 256; } }; @@ -574,7 +540,7 @@ namespace nil { tables["keccak_normalize6_table"] = std::shared_ptr(new normalize_base8_table_type(6)); tables["keccak_chi_table"] = std::shared_ptr(new chi_table_type()); tables["byte_range_table"] = std::shared_ptr(new byte_range_table_type()); - tables["zkevm_opcodes"] = std::shared_ptr(new zkevm_opcode_table()); + tables["opcode_push_size"] = std::shared_ptr(new opcode_push_size_table()); tables["byte_and_xor_table"] = std::shared_ptr(new byte_and_xor_table_type()); } diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/big_field/circuits/bytecode.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/big_field/circuits/bytecode.hpp index e5c6f56ee5..6673e3cc6a 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/big_field/circuits/bytecode.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/big_field/circuits/bytecode.hpp @@ -185,9 +185,8 @@ namespace nil::blueprint::bbf::zkevm_big_field{ std::vector tmp = {context_object.relativize(tag[0] * value[0], -1)}; context_object.relative_lookup(tmp, "byte_range_table/full", 0, max_bytecode_size - 1); tmp = {context_object.relativize(std::vector({value[0] * is_opcode[0], - push_size[0] * is_opcode[0], - is_opcode[0]}), -1)}; - context_object.relative_lookup(tmp, "zkevm_opcodes/full", 0, max_bytecode_size - 1); + push_size[0] * is_opcode[0]}), -1)}; + context_object.relative_lookup(tmp, "opcode_push_size/full", 0, max_bytecode_size - 1); tmp = {context_object.relativize(std::vector({ tag[1] + 1 - tag[1], tag[0] * (1 - tag[1]) * value_rlc[0], @@ -198,4 +197,4 @@ namespace nil::blueprint::bbf::zkevm_big_field{ } }; }; -} \ No newline at end of file +} diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/bytecode.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/bytecode.hpp index 394fc8cb18..b1803e08e9 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/bytecode.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/bytecode.hpp @@ -1,5 +1,6 @@ //---------------------------------------------------------------------------// // Copyright (c) 2024 Elena Tatuzova +// Copyright (c) 2025 Alexander Vasilyev // // MIT License // @@ -30,323 +31,331 @@ #include namespace nil::blueprint::bbf::zkevm_small_field{ - template - class bytecode : public generic_component { - using typename generic_component::context_type; - using generic_component::allocate; - using generic_component::copy_constrain; - using generic_component::constrain; - using generic_component::lookup; - using generic_component::lookup_table; - - using BytecodeTable = bytecode_table; - using KeccakTable = keccak_table; - using BytecodeHashTable = bytecode_hash_table; - - public: - using typename generic_component::table_params; - using typename generic_component::TYPE; - - struct input_type { - TYPE rlc_challenge; - - BytecodeTable::input_type bytecodes; - KeccakTable::private_input_type keccak_buffers; + +template +class bytecode : public generic_component { + using typename generic_component::context_type; + using generic_component::allocate; + using generic_component::copy_constrain; + using generic_component::constrain; + using generic_component::lookup; + using generic_component::lookup_table; + + using BytecodeTable = bytecode_table; + using KeccakTable = keccak_table; + using BytecodeHashTable = bytecode_hash_table; + + public: + using typename generic_component::table_params; + using typename generic_component::TYPE; + + struct input_type { + TYPE rlc_challenge; + + BytecodeTable::input_type bytecodes; + KeccakTable::private_input_type keccak_buffers; + }; + + size_t max_bytecode_size; + size_t max_keccak_blocks; + size_t max_bytecodes_amount; + + static table_params get_minimal_requirements( + size_t max_bytecode_size, + size_t max_keccak_blocks, + size_t max_bytecodes_amount) { + BOOST_ASSERT(max_bytecode_size > max_keccak_blocks + max_bytecodes_amount); + return { + .witnesses = BytecodeTable::get_witness_amount() + std::max(KeccakTable::get_witness_amount(), BytecodeHashTable::get_witness_amount()) + 16, + .public_inputs = 1, + .constants = 10, + .rows = max_bytecode_size }; + } - std::size_t max_bytecode_size; - std::size_t max_keccak_blocks; - std::size_t max_bytecodes_amount; - - static table_params get_minimal_requirements( - std::size_t max_bytecode_size, - std::size_t max_keccak_blocks, - std::size_t max_bytecodes_amount - ) { - BOOST_ASSERT(max_bytecode_size > max_keccak_blocks + max_bytecodes_amount); - return { - .witnesses = BytecodeTable::get_witness_amount() + std::max(KeccakTable::get_witness_amount(), BytecodeHashTable::get_witness_amount()) + 16, - .public_inputs = 1, - .constants = 10, - .rows = max_bytecode_size - }; + static void allocate_public_inputs( + context_type &context, + input_type &input, + size_t max_bytecode_size, + size_t max_keccak_blocks, + size_t max_bytecodes_amount) { + context.allocate(input.rlc_challenge, 0, 0, column_type::public_input); + } + + bytecode(context_type &context_object, input_type input, + size_t max_bytecode_size_, size_t max_keccak_blocks_, + size_t max_bytecodes_amount_) + : max_bytecode_size(max_bytecode_size_), + max_keccak_blocks(max_keccak_blocks_), + max_bytecodes_amount(max_bytecodes_amount_), + generic_component(context_object) { + BOOST_LOG_TRIVIAL(trace) << "Small field bytecode circuit assignment" << std::endl; + + size_t current_column = 0; + std::vector bytecode_lookup_area; + for (size_t i = 0; i < BytecodeTable::get_witness_amount(); ++i) { + bytecode_lookup_area.push_back(current_column++); } + context_type bytecode_ct = context_object.subcontext(bytecode_lookup_area,0,max_bytecode_size); + BytecodeTable bc_t(bytecode_ct, input.bytecodes, max_bytecode_size); - static void allocate_public_inputs( - context_type &context, input_type &input, - std::size_t max_bytecode_size, - std::size_t max_keccak_blocks, - std::size_t max_bytecodes_amount - ) { - context.allocate(input.rlc_challenge, 0, 0, column_type::public_input); + size_t bytecode_hash_column = current_column; + std::vector bytecode_hash_lookup_area; + for (size_t i = 0; i < BytecodeHashTable::get_witness_amount(); ++i) { + bytecode_hash_lookup_area.push_back(bytecode_hash_column++); } + context_type bytecode_hash_ct = context_object.subcontext(bytecode_hash_lookup_area,0,max_bytecodes_amount); + BytecodeHashTable bytecode_hash_t(bytecode_hash_ct, input.bytecodes, max_bytecodes_amount); - bytecode(context_type &context_object, - input_type input, - std::size_t max_bytecode_size_, - std::size_t max_keccak_blocks_, - std::size_t max_bytecodes_amount_ - ) : max_bytecode_size(max_bytecode_size_), - max_keccak_blocks(max_keccak_blocks_), - max_bytecodes_amount(max_bytecodes_amount_), - generic_component(context_object) - { - BOOST_LOG_TRIVIAL(trace) << "Small field bytecode circuit assignment" << std::endl; - - std::size_t current_column = 0; - std::vector bytecode_lookup_area; - for( std::size_t i = 0; i < BytecodeTable::get_witness_amount(); i++){ - bytecode_lookup_area.push_back(current_column++); - } - context_type bytecode_ct = context_object.subcontext(bytecode_lookup_area,0,max_bytecode_size); - BytecodeTable bc_t(bytecode_ct, input.bytecodes, max_bytecode_size); + size_t keccak_column = current_column; + std::vector keccak_lookup_area; + for (size_t i = 0; i < KeccakTable::get_witness_amount(); ++i) { + keccak_lookup_area.push_back(keccak_column++); + } + context_type keccak_ct = context_object.subcontext(keccak_lookup_area,max_bytecodes_amount,max_keccak_blocks); + KeccakTable keccak_t(keccak_ct, {input.rlc_challenge, input.keccak_buffers}, max_keccak_blocks); - std::size_t bytecode_hash_column = current_column; - std::vector bytecode_hash_lookup_area; - for( std::size_t i = 0; i < BytecodeHashTable::get_witness_amount(); i++){ - bytecode_hash_lookup_area.push_back(bytecode_hash_column++); - } - context_type bytecode_hash_ct = context_object.subcontext(bytecode_hash_lookup_area,0,max_bytecodes_amount); - BytecodeHashTable bytecode_hash_t(bytecode_hash_ct, input.bytecodes, max_bytecodes_amount); + const std::vector &tag = bc_t.tag; + const std::vector &index = bc_t.index; + const std::vector &value = bc_t.value; + const std::vector &is_opcode = bc_t.is_opcode; + const std::vector &bytecode_id = bc_t.bytecode_id; + std::vector length(max_bytecode_size); + std::vector value_rlc(max_bytecode_size); + std::vector push_size(max_bytecode_size); + std::vector push_size_inv(max_bytecode_size); + std::vector bytecode_end_witness(max_bytecode_size); + std::vector is_padding(max_bytecode_size); + std::vector rlc_challenge(max_bytecode_size); + std::vector bytecode_rlc(max_bytecodes_amount); - std::size_t keccak_column = current_column; - std::vector keccak_lookup_area; - for( std::size_t i = 0; i < KeccakTable::get_witness_amount(); i++){ - keccak_lookup_area.push_back(keccak_column++); - } - context_type keccak_ct = context_object.subcontext(keccak_lookup_area,max_bytecodes_amount,max_keccak_blocks); - KeccakTable keccak_t(keccak_ct, {input.rlc_challenge, input.keccak_buffers}, max_keccak_blocks); - - const std::vector &tag = bc_t.tag; - const std::vector &index = bc_t.index; - const std::vector &value = bc_t.value; - const std::vector &is_opcode = bc_t.is_opcode; - const std::vector &bytecode_id = bc_t.bytecode_id; - std::vector rlc_challenge(max_bytecode_size); - std::vector push_size(max_bytecode_size); - std::vector length_left(max_bytecode_size); - std::vector metadata_count(max_bytecode_size); - std::vector value_rlc(max_bytecode_size); - std::vector is_header(max_bytecode_size); - std::vector is_executed(max_bytecode_size); - std::vector is_metadata(max_bytecode_size); - std::vector hash_value_rlc(max_bytecodes_amount); - std::vector is_last_byte(max_bytecode_size); - - if constexpr (stage == GenerationStage::ASSIGNMENT) { - const auto &bytecodes = input.bytecodes.get_data(); - std::size_t cur = 0; - - for(std::size_t i = 0; i < bytecodes.size(); i++) { - const auto &buffer = bytecodes[i].first; - std::size_t total_len = buffer.size(); - - // Determine the boundary between executable bytes and metadata - std::size_t exec_boundary = total_len; // Default: all bytes are executable - if (total_len >= 2) { - // Metadata length is encoded in the last two bytes - std::size_t meta_len = (buffer[total_len - 2] << 8) + buffer[total_len - 1]; - - if (meta_len + 2 <= total_len) { - std::size_t boundary = total_len - meta_len - 2 - 1; // Byte before metadata - // Check for stopping opcodes (STOP, INVALID, RETURN) that will - // confirm the length of the metadata - if (boundary < total_len && - (buffer[boundary] == 0x00 || buffer[boundary] == 0xfe || - buffer[boundary] == 0xf3) - ) { - exec_boundary = boundary + 1; // Set boundary after the stopping opcode - } - } - } - BOOST_LOG_TRIVIAL(trace) << "Bytecode " << i << " size = " << total_len; - BOOST_LOG_TRIVIAL(trace) << "Executable bytes boundary: " << exec_boundary; - - // Header - length_left[cur] = total_len; - metadata_count[cur] = 0; - value_rlc[cur] = total_len; - is_header[cur] = 1; - rlc_challenge[cur] = input.rlc_challenge; - cur++; - - // Bytes - std::size_t push_size_value = 0; - for(std::size_t j = 0; j < buffer.size(); j++, cur++){ - length_left[cur] = length_left[cur - 1] - 1; - if( j < exec_boundary ){ - metadata_count[cur] = 0; - is_executed[cur] = 1; - } else { - metadata_count[cur] = metadata_count[cur - 1] + 1; - is_metadata[cur] = 1; - } - auto byte = buffer[j]; - if (push_size_value == 0) { - if (byte > 0x5f && byte < 0x80) push_size_value = byte - 0x5f; // Set PUSH size - } else { - push_size_value--; - } - push_size[cur] = push_size_value; - rlc_challenge[cur] = input.rlc_challenge; - value_rlc[cur] = value_rlc[cur - 1] * input.rlc_challenge + byte; - if( is_opcode[cur] == 1 ) - BOOST_LOG_TRIVIAL(trace) << cur << ". " << std::hex << index[cur] << " " << opcode_from_number(byte); - else if (is_executed[cur] == 1) - BOOST_LOG_TRIVIAL(trace) << cur << ". " << std::hex << index[cur] << " Data 0x" << std::setw(2) << std::setfill('0') << std::size_t(byte) << std::dec; - else - BOOST_LOG_TRIVIAL(trace) << cur << ". " << std::hex << index[cur] << " Metadata 0x" << std::setw(2) << std::setfill('0') << std::size_t(byte) << std::dec; - } - is_last_byte[cur - 1] = 1; - hash_value_rlc[i] = value_rlc[cur - 1]; - } - } + if constexpr (stage == GenerationStage::ASSIGNMENT) { + const auto &bytecodes = input.bytecodes.get_data(); + rlc_challenge.assign(max_bytecode_size, input.rlc_challenge); - std::size_t is_last_byte_index = 0; - std::size_t index_index = bytecode_lookup_area[1]; - std::size_t bytecode_id_index = bytecode_lookup_area[4]; - std::size_t value_rlc_index = bytecode_lookup_area[5]; - std::size_t last_column = 0; - for( std::size_t i = 0; i < max_bytecode_size; i++ ){ - current_column = BytecodeTable::get_witness_amount() + std::max(KeccakTable::get_witness_amount(), BytecodeHashTable::get_witness_amount()); - allocate(length_left[i], current_column++, i); - allocate(metadata_count[i], current_column++, i); - value_rlc_index = current_column; allocate(value_rlc[i], current_column++, i); - allocate(push_size[i], current_column++, i); - allocate(rlc_challenge[i], current_column++, i); - allocate(is_header[i], current_column++, i); - allocate(is_executed[i], current_column++, i); - allocate(is_metadata[i], current_column++, i); - is_last_byte_index = current_column; allocate(is_last_byte[i], current_column++, i); - last_column = current_column; - } - for( std::size_t i = 0; i < max_bytecodes_amount; i++ ){ - allocate(hash_value_rlc[i], last_column, i); - } + is_padding[0] = 1; + bytecode_end_witness[0] = 1; + + size_t row = 1; + size_t current_index = 0; + size_t push_size_value = 0; - constrain(bytecode_id[0] - 1); - constrain(is_header[0] - 1); - if constexpr (stage == GenerationStage::CONSTRAINTS) { - std::vector every_row_constraints; - std::vector non_first_row_constraints; - - // 0. Dynamic selectors may be only 0 or 1 - every_row_constraints.push_back(is_header[1] * (is_header[1] - 1)); - every_row_constraints.push_back(is_executed[1] * (is_executed[1] - 1)); - every_row_constraints.push_back(is_metadata[1] * (is_metadata[1] - 1)); - // 1. Only one of them may be 1 on a row - TYPE is_filled = is_header[1] + is_executed[1] + is_metadata[1]; - TYPE is_padding = 1 - is_filled; - every_row_constraints.push_back(is_filled * (is_filled -1)); - // 2. TAG is zeroes, one, two or three - // 0 -- padding - // 1 -- HEADER - // 2 -- BYTE - // 3 -- METADATA - every_row_constraints.push_back(tag[1] - is_header[1] * 1 - is_executed[1] * 2 - is_metadata[1] * 3); - // 3. For HEADER index is 0 - every_row_constraints.push_back(is_header[1] * index[1]); - // 4. In contract header length_left == contract length - every_row_constraints.push_back(is_header[1] * (length_left[1] - value[1])); - // 5. is_opcode is zeroes or ones - every_row_constraints.push_back(is_opcode[1] * (is_opcode[1] - 1)); - // 6. is_opcode on HEADER are zeroes - every_row_constraints.push_back(is_header[1] * is_opcode[1]); - // 7. value_rlc for HEADERS == length_left - every_row_constraints.push_back(is_header[1] * (value_rlc[1] - length_left[1])); - - // 8. INDEX for first contract byte is zero - non_first_row_constraints.push_back(is_header[0] * index[1]); - // 9. INDEX is incremented for all bytes - non_first_row_constraints.push_back((1 - is_header[0]) * (is_executed[1] + is_metadata[1]) * (index[1] - index[0] - 1)); - // 10. Length_left is zero for last byte in the contract - non_first_row_constraints.push_back(is_last_byte[1] * length_left[1]); - // 11. First is_opcode on BYTE after HEADER is 1 - non_first_row_constraints.push_back(is_header[0] * is_executed[1] * (is_opcode[1] - 1)); - // 12. PUSH_SIZE decreases for non-opcodes except metadata - non_first_row_constraints.push_back(is_executed[1] * (1 - is_opcode[1]) * (push_size[0] - push_size[1] - 1)); // Append tag_selectors - // 13. before opcode push_size is always zero - non_first_row_constraints.push_back(is_opcode[1] * push_size[0]); - // 14. for all bytes bytecode_id is similar to previous - non_first_row_constraints.push_back((is_executed[1] + is_metadata[1]) * (bytecode_id[0] - bytecode_id[1])); - // 15. for all bytes RLC is correct - non_first_row_constraints.push_back((is_executed[1] + is_metadata[1]) * (value_rlc[1] - value_rlc[0] * rlc_challenge[1] - value[1])); - // 16. for each BYTEs rlc_challenge are similar - non_first_row_constraints.push_back(is_filled * (rlc_challenge[1] - rlc_challenge[0])); - // 17. is_last_byte is correctly defined - non_first_row_constraints.push_back(is_last_byte[0] - (is_header[1] + is_padding) * (is_metadata[0] + is_executed[0])); - // 18. bytecode_id increased for each bytecode - non_first_row_constraints.push_back(is_header[1] * (bytecode_id[1] - bytecode_id[0] - 1)); - // 19. After metadata is metadata or padding - non_first_row_constraints.push_back(is_metadata[0] * (is_metadata[1] + is_padding - 1)); - // 20. If metadata, is_opcode = 0 - every_row_constraints.push_back(is_metadata[1] * is_opcode[1]); - // 21. Metadata_count does not change if is_executed - non_first_row_constraints.push_back((is_executed[1]) * (metadata_count[1] - metadata_count[0])); - // 22. Metadata count inrement by 1 for metadata - non_first_row_constraints.push_back(is_metadata[1] * (metadata_count[1] - metadata_count[0] - 1)); - // 24. Metadata count is equal to last 2 metadata bytes - non_first_row_constraints.push_back(is_last_byte[1] * metadata_count[1] * (value[1] + value[0] * 256 - metadata_count[1] + 2)); - // 25 Length left decrease by 1 if not padding - non_first_row_constraints.push_back((length_left[0] - length_left[1] - 1) * (is_executed[1] + is_metadata[1])); - // 26. After padding is always padding - every_row_constraints.push_back(is_padding * (is_header[2] + is_executed[2] + is_metadata[2])); - // 27. Last is always padding - constrain(is_header[max_bytecode_size - 1] + is_executed[max_bytecode_size - 1] + is_metadata[max_bytecode_size - 1]); - - // Lookup_table - BOOST_LOG_TRIVIAL(trace) << "zkevm_bytecode_data_with_rlc " - << is_last_byte_index << " " - << bytecode_id_index << " " - << index_index << " " - << value_rlc_index; - context_object.lookup_table("zkevm_bytecode_data_with_rlc", { - is_last_byte_index, - bytecode_id_index, - index_index, - value_rlc_index, - }, 0, max_bytecode_size-1); - - for( auto& constraint: every_row_constraints){ - context_object.relative_constrain(context_object.relativize(constraint, -1), 0, max_bytecode_size-1); + auto add_byte = [&](uint8_t byte, bool padding) { + length[row] = length[row - 1]; + value_rlc[row] = value_rlc[row - 1] * input.rlc_challenge + byte; + + if (push_size_value == 0) { + if (byte >= 0x60 && byte <= 0x7f) + push_size_value = byte - 0x5f; + } else { + --push_size_value; } - for( auto &constraint: non_first_row_constraints ){ - context_object.relative_constrain(context_object.relativize(constraint, -1), 1, max_bytecode_size - 1); + + push_size[row] = push_size_value; + push_size_inv[row] = push_size[row] == 0 ? 0 : push_size[row].inversed(); + + auto length_diff = length[row] - (current_index + 1); + bytecode_end_witness[row] = length_diff == 0 ? 0 : length_diff.inversed(); + + is_padding[row] = padding; + + ++current_index, ++row; + }; + + for (size_t i = 0; i < bytecodes.size(); ++i) { + const auto &buffer = bytecodes[i].first; + BOOST_LOG_TRIVIAL(trace) << "Bytecode " << i << " size = " << buffer.size(); + + // Header + current_index = 0; + length[row] = value_rlc[row] = buffer.size(); + bytecode_end_witness[row] = length[row] == 0 ? 0 : length[row].inversed(); + ++row; + + // Bytes + for (uint8_t byte : buffer) { + BOOST_LOG_TRIVIAL(trace) << row << ". " << current_index; + if (is_opcode[row] == 0) { + BOOST_LOG_TRIVIAL(trace) + << " Push data 0x" << std::hex << std::setw(2) + << std::setfill('0') << size_t(byte) << std::dec; + } else if (auto opcode = opcode_from_number(byte); opcode != static_cast(-1)) { + BOOST_LOG_TRIVIAL(trace) + << ' ' << opcode_to_string(opcode); + } else { + BOOST_LOG_TRIVIAL(trace) + << " Unknown opcode 0x" << std::hex << std::setw(2) + << std::setfill('0') << size_t(byte) << std::dec; + } + + add_byte(byte, false); } - // Lookups - std::vector tmp = {(is_executed[1] + is_metadata[1]) * value[1]}; - context_object.relative_lookup(context_object.relativize(tmp, -1), "byte_range_table/full", 0, max_bytecode_size); - - tmp = { - value[1] * is_opcode[1], - push_size[1] * is_opcode[1], - is_opcode[1] - }; - context_object.relative_lookup(context_object.relativize(tmp, -1), "zkevm_opcodes/full", 0, max_bytecode_size); - - tmp = { - is_last_byte[1], - is_last_byte[1] * bytecode_id[1], - is_last_byte[1] * (index[1] + 1), - }; - context_object.relative_lookup(context_object.relativize(tmp, -1), "zkevm_bytecode_hash", 0, max_bytecode_size); - - tmp = { - bytecode_hash_t.tag[1], - bytecode_hash_t.tag[1] * bytecode_hash_t.bytecode_id[1], - bytecode_hash_t.tag[1] * (bytecode_hash_t.bytecode_size[1] - 1), - bytecode_hash_t.tag[1] * hash_value_rlc[1], - }; - context_object.relative_lookup(context_object.relativize(tmp, -1), "zkevm_bytecode_data_with_rlc", 0, max_bytecodes_amount-1); - - tmp = { - hash_value_rlc[1] - }; - for( std::size_t i = 0; i < 16; i++){ - tmp.push_back(bytecode_hash_t.bytecode_hash[1][i]); + + bytecode_rlc[i] = value_rlc[row - 1]; + + // Implicit zero bytes + BOOST_ASSERT(row + push_size_value + 1 < max_bytecode_size); + + while (push_size_value > 0) { // missing push arguments + add_byte(0, true); } - context_object.relative_lookup(context_object.relativize(tmp, -1), "keccak_table", 0, max_bytecodes_amount-1); + + add_byte(0, true); } - }; + + while (row < max_bytecode_size) add_byte(0, true); + } + + size_t bytecode_id_index = bytecode_lookup_area[4]; + size_t bytecode_length_index; + size_t value_rlc_index; + size_t bytecode_end_witness_index; + size_t last_column = 0; + for (size_t i = 0; i < max_bytecode_size; ++i) { + current_column = BytecodeTable::get_witness_amount() + std::max(KeccakTable::get_witness_amount(), BytecodeHashTable::get_witness_amount()); + allocate(rlc_challenge[i], current_column++, i); + allocate(length[i], bytecode_length_index = current_column++, i); + allocate(value_rlc[i], value_rlc_index = current_column++, i); + allocate(push_size[i], current_column++, i); + allocate(push_size_inv[i], current_column++, i); + allocate(bytecode_end_witness[i], bytecode_end_witness_index = current_column++, i); + allocate(is_padding[i], current_column++, i); + last_column = current_column; + } + + for (size_t i = 0; i < max_bytecodes_amount; ++i) { + allocate(bytecode_rlc[i], last_column, i); + } + + if constexpr (stage == GenerationStage::CONSTRAINTS) { + // Row 0 is used to allow all-zeros lookups + constrain(tag[0], "tag is 0 in row 0"); + constrain(bytecode_id[0], "bytecode_id is 0 in row 0"); + + auto constrain_rows = [&](TYPE c, const std::string &name = "") { + context_object.relative_constrain( + context_object.relativize(c, -1), 1, max_bytecode_size-1, + name); + }; + + // RLC challenge correctness: + copy_constrain(rlc_challenge[0], input.rlc_challenge); + constrain_rows(rlc_challenge[1] - rlc_challenge[0], "RLC challenge"); + + // Tag is 0 for headers and 1 for byte rows. + constrain_rows(tag[1] * (tag[1] - 1), "tag is 0 or 1"); + + // First row must be a header: else, it would be possible to replace + // the first bytecode with its suffix, since checks for the initial + // values of index and rlc_value are done on header lines. + constrain(tag[1], "tag is 0 in row 1"); + + constrain_rows((1 - tag[1]) * (bytecode_id[1] - (bytecode_id[0] + 1)), "On a header bytecode_id increased"); + + // For bytes, bytecode id and length are same as in header. + constrain_rows(tag[1] * (bytecode_id[1] - bytecode_id[0]), "bytecode_id doesn't change on bytes rows"); + constrain_rows(tag[1] * (length[1] - length[0]), "length validity for bytes"); + constrain_rows((1 - tag[1]) * (length[1] - value[1]), "value on header rows is bytecode length"); + + // Index is 0 in headers to allow accumulated length computation. + constrain_rows((1 - tag[1]) * index[1], "header index is 0"); + // For bytes, index values start from 0 and increment sequentially. + constrain_rows((1 - tag[0]) * index[1] + tag[1] * tag[0] * (index[1] - (index[0] + 1)), "byte index increased correctly"); + // Note that we can skip tag[1] factor in the first part, since + // index is 0 in headers anyway. + + // In headers, RLC is reset to bytecode length and then accumulates + // byte values. + constrain_rows((1 - tag[1]) * (value_rlc[1] - length[1]) + + tag[1] * (value_rlc[1] - + (value_rlc[0] * rlc_challenge[1] + value[1])), + "value RLC definition"); + + // Push size is set to non-zero value at push opcodes, this is + // controlled by a lookup below. After that, it must go all the way + // to zero. + constrain_rows(push_size[0] * (push_size[1] - (push_size[0] - 1)), + "push size decreases to zero"); + // Opcodes are all the bytes that are not used as push arguments, + constrain_rows(is_opcode[1] - tag[1] * (1 - push_size[0] * push_size_inv[0]), + "opcode definition"); + constrain_rows(push_size[1] * (1 - push_size[1] * push_size_inv[1]), + "push_size_inv definition"); + // and it also works for the first byte if push size is zero in headers. + constrain_rows((1 - tag[1]) * push_size[1], "push size is 0 in header"); + // Yep, this is mutual recursion. + + // Finally, bytecode_end marks a row where RLC is finalized, either + // header row of an empty bytecode, or last (explicit) byte otherwise. + TYPE length_diff = length[1] - (tag[1] + index[1]); + constrain_rows(length_diff * (1 - length_diff * bytecode_end_witness[1]), + "witness for bytecode end condition"); + TYPE is_bytecode_end = 1 - length_diff * bytecode_end_witness[1]; + + // After bytecode ends, there are padding zeros: potentially + // missing push arguments and implicitly defined STOP opcode. + TYPE prev_length_diff = length[0] - (tag[0] + index[0]); + TYPE prev_is_end = 1 - prev_length_diff * bytecode_end_witness[0]; + constrain_rows(is_padding[1] - tag[1] * (prev_is_end + is_padding[0]), + "padding definition"); + constrain_rows(is_padding[1] * value[1], "padding bytes are 0s"); + // Note that it is safe mark row 0 as padding. + + // We need to make sure that if a bytecode is present in the table, + // it's hash is checked, i.e. it reaches its last byte. + // We do this by checking for padding row before new bytecode + // starts and at the end of the table; if there is a padding row, + // there must be the bytecode end row before too. + // Note that we do not require all potentially used padding bytes + // to be present: it's ok if lookup fails with malformed assignment, + // we only care for false positive validity checks. + constrain_rows((1 - tag[1]) * (1 - is_padding[0]), "bytecode is completed"); + constrain(is_padding[max_bytecode_size - 1] - 1, "last row is padding"); + + // Lookup_table + BOOST_LOG_TRIVIAL(trace) << "zkevm_bytecode_rlc " + << bytecode_id_index << ' ' + << bytecode_length_index << ' ' + << value_rlc_index << ' ' + << bytecode_end_witness_index << std::endl; + context_object.lookup_table("zkevm_bytecode_rlc", { + bytecode_id_index, + bytecode_length_index, + value_rlc_index, + bytecode_end_witness_index, + }, 0, max_bytecode_size - 1); + + // Lookups + std::vector tmp = { + is_opcode[1] * value[1], + is_opcode[1] * push_size[1] + }; + context_object.relative_lookup(context_object.relativize(tmp, -1), "opcode_push_size/full", 0, max_bytecode_size-1); + + tmp = { + is_bytecode_end, + is_bytecode_end * bytecode_id[1], + is_bytecode_end * length[1] + }; + context_object.relative_lookup(context_object.relativize(tmp, -1), "zkevm_bytecode_hash", 1, max_bytecode_size-1); + + tmp = { + bytecode_hash_t.tag[1] * bytecode_hash_t.bytecode_id[1], + bytecode_hash_t.tag[1] * bytecode_hash_t.bytecode_size[1], + bytecode_hash_t.tag[1] * bytecode_rlc[1], + 1 - bytecode_hash_t.tag[1], // if witness is 0, then length_diff is 0! + }; + context_object.relative_lookup(context_object.relativize(tmp, -1), "zkevm_bytecode_rlc", 0, max_bytecodes_amount-1); + + tmp = { + bytecode_rlc[1] + }; + for( size_t i = 0; i < 16; i++){ + tmp.push_back(bytecode_hash_t.bytecode_hash[1][i]); + } + context_object.relative_lookup(context_object.relativize(tmp, -1), "keccak_table", 0, max_bytecodes_amount-1); + } }; -} \ No newline at end of file +}; + +} diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/zkevm.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/zkevm.hpp index ab057bba4f..87731e2d60 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/zkevm.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/circuits/zkevm.hpp @@ -370,7 +370,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ } // 3. Gas chunks range checks context_object.relative_lookup({context_object.relativize(gas_chunks[1][0], -1)}, "chunk_16_bits/full", 0, max_zkevm_rows-1); - context_object.relative_lookup({context_object.relativize((MAX_ZKEVM_GAS_BOUND >> 16 - 1) - gas_chunks[1][0], -1)}, "chunk_16_bits/full", 0, max_zkevm_rows-1); + context_object.relative_lookup({context_object.relativize(((MAX_ZKEVM_GAS_BOUND >> 16) - 1) - gas_chunks[1][0], -1)}, "chunk_16_bits/full", 0, max_zkevm_rows-1); context_object.relative_lookup({context_object.relativize(gas_chunks[1][1], -1)}, "chunk_16_bits/full", 0, max_zkevm_rows-1); std::vector> erc; // every row constraints @@ -406,8 +406,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ opcode_selector_sum += opcode_selectors[1 + j][opcode_id]; current_opcode_constraint += opcode_selectors[1 + j][opcode_id] * opcode_to_number(current_opcode); zkevm_opcode_row_selectors[{current_opcode, j}] = opcode_selectors[1 + j][opcode_id]; - // STOP opcode logic is controlled by opcode - if( nil_opcodes.count(current_opcode) == 0 && current_opcode != zkevm_opcode::STOP ){ + if (!nil_opcodes.contains(current_opcode)) { evm_opcode_constraint += opcode_selectors[1 + j][opcode_id]; } if( current_opcode != zkevm_opcode::error_gas ){ @@ -587,7 +586,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ } std::vector tmp(5); - tmp[0] = context_object.relativize(evm_opcode_constraint * 2, -1); + tmp[0] = context_object.relativize(evm_opcode_constraint, -1); tmp[1] = context_object.relativize(evm_opcode_constraint * all_states[1].pc, -1); tmp[2] = context_object.relativize(evm_opcode_constraint * all_states[1].opcode, -1); tmp[3] = context_object.relativize(evm_opcode_constraint, -1); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/codecopy.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/codecopy.hpp index 026e10cd7a..8c7dcf6cbe 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/codecopy.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/codecopy.hpp @@ -114,11 +114,11 @@ namespace nil::blueprint::bbf::zkevm_small_field { // 4. Process offset std::array offset_chunks; if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) - << "\td_overflow: " << d_range.is_overflow - << ", l_overflow: " << l_range.is_overflow - << ", is_length_zero: " << is_length_zero - << ", overflow: " << is_overflow; + // BOOST_LOG_TRIVIAL(trace) + // << "\td_overflow: " << d_range.is_overflow + // << ", l_overflow: " << l_range.is_overflow + // << ", is_length_zero: " << is_length_zero + // << ", overflow: " << is_overflow; auto o_chunks = w_to_16(current_state.stack_top(1)); for (std::size_t i = 0; i < 16; i++) { offset_chunks[i] = o_chunks[i]; @@ -152,9 +152,9 @@ namespace nil::blueprint::bbf::zkevm_small_field { context_type new_memory_word_size_ct = context_object.subcontext(word_size_area, 0, 1); Word_Size max_written_obj(new_memory_word_size_ct, (d_range.value + l_range.value) * (1 - is_length_zero - is_overflow + is_length_zero * is_overflow)); TYPE max_written = max_written_obj.size; - if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) << "\tmax_written word: " << std::hex << max_written << std::dec; - } + // if constexpr (stage == GenerationStage::ASSIGNMENT) { + // BOOST_LOG_TRIVIAL(trace) << "\tmax_written word: " << std::hex << max_written << std::dec; + // } // 7. Calculate length in words (need for gas consumption calculation) context_type length_words_ct = context_object.subcontext(word_size_area, 1, 1); @@ -203,16 +203,16 @@ namespace nil::blueprint::bbf::zkevm_small_field { TYPE is_gas_error = is_overflow + is_gas_error_obj.gt - is_overflow * is_gas_error_obj.gt; allocate(is_gas_error, 45, 2); - if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) - << "\tcurrent_gas: "<< current_gas - << " current memory cost: " << current_memory_cost - << " new memory cost: " << new_memory_cost - << " pre-cost: " << pre_cost - << " current_memory: " << current_memory - << " new_memory: " << new_memory - << " is_gas_error: " << is_gas_error; - } + // if constexpr (stage == GenerationStage::ASSIGNMENT) { + // BOOST_LOG_TRIVIAL(trace) + // << "\tcurrent_gas: "<< current_gas + // << " current memory cost: " << current_memory_cost + // << " new memory cost: " << new_memory_cost + // << " pre-cost: " << pre_cost + // << " current_memory: " << current_memory + // << " new_memory: " << new_memory + // << " is_gas_error: " << is_gas_error; + // } // 12. Check offset+length ? bytecode_size TYPE offset = offset_chunks[15]; @@ -254,18 +254,18 @@ namespace nil::blueprint::bbf::zkevm_small_field { TYPE zero_lookup_length = need_zero_copy_lookup * (length - bytecode_lookup_length); allocate(zero_lookup_length, 45, 0); - if constexpr( stage == GenerationStage::ASSIGNMENT){ - BOOST_LOG_TRIVIAL(trace) << "\t" - << "offset: " << offset - << " length: " << length - << " bytecode_size: " << bytecode_size - << " is_offset_overflow: " << is_offset_overflow; - BOOST_LOG_TRIVIAL(trace) << "\t" - << "need_bytecode_copy_lookup: " << need_bytecode_copy_lookup - << " need_zero_copy_lookup: " << need_zero_copy_lookup - << " zero_lookup_length: " << zero_lookup_length - << " bytecode_lookup_length: " << bytecode_lookup_length; - } + // if constexpr( stage == GenerationStage::ASSIGNMENT){ + // BOOST_LOG_TRIVIAL(trace) << "\t" + // << "offset: " << offset + // << " length: " << length + // << " bytecode_size: " << bytecode_size + // << " is_offset_overflow: " << is_offset_overflow; + // BOOST_LOG_TRIVIAL(trace) << "\t" + // << "need_bytecode_copy_lookup: " << need_bytecode_copy_lookup + // << " need_zero_copy_lookup: " << need_zero_copy_lookup + // << " zero_lookup_length: " << zero_lookup_length + // << " bytecode_lookup_length: " << bytecode_lookup_length; + // } if constexpr (stage == GenerationStage::CONSTRAINTS) { constrain(current_state.pc_next() - current_state.pc(2) - 1); // PC transition @@ -304,7 +304,7 @@ namespace nil::blueprint::bbf::zkevm_small_field { // Prove bytecode size lookup({ - TYPE(1), // HEADER + TYPE(0), // HEADER TYPE(0), // PC bytecode_size, // bytecode_size TYPE(0), // is_opcode diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jump.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jump.hpp index c846e1c478..96a014e48f 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jump.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jump.hpp @@ -71,7 +71,7 @@ namespace nil::blueprint::bbf::zkevm_small_field { ), "zkevm_rw_256"); // JUMP may be done only to JUMPDEST destination lookup({ - TYPE(2), // It's executed opcode, not header, not metadata + TYPE(1), // It's executed opcode, not header, not metadata addr_chunks[15], 0x5b, // JUMPDEST opcode TYPE(1), // is_opcode = 1 diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jumpi.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jumpi.hpp index c0e7a9d706..a407549ce3 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jumpi.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/jumpi.hpp @@ -103,7 +103,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ ), "zkevm_rw_256"); // JUMP may be done only to JUMPDEST destination lookup({ - is_jump * TYPE(2), // It's executed opcode, not header, not metadata + is_jump * TYPE(1), // It's executed opcode, not header, not metadata is_jump * addr, is_jump * 0x5b, // JUMPDEST opcode is_jump * TYPE(1), // is_opcode = 1 diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mload.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mload.hpp index 29b6eccf97..994f9c8626 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mload.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mload.hpp @@ -99,9 +99,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ TYPE current_mem; if constexpr (stage == GenerationStage::ASSIGNMENT) { current_mem = (current_state.memory_size() + 31) / 32; - BOOST_LOG_TRIVIAL(trace) << "\t" - << "Offset = " << std::hex << current_state.stack_top() - << " is_overflow: " << is_overflow << std::dec; } allocate(current_mem, 46, 1); Max_30 new_memory_obj(new_memory_ct, word_size_obj.size, current_mem); @@ -109,10 +106,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ TYPE new_mem = new_memory_obj.max; allocate(new_mem, 47, 1); - if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) << "\tmemory_size:" << current_mem << " => " << new_mem; - } - // 4. Calculate proposed operation gas cost context_type current_memory_cost_ct = context_object.subcontext({3, 4, 5, 6, 7, 8}, 1, 1); Memory_Cost current_memory_cost_obj(current_memory_cost_ct, current_mem); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore.hpp index 738a3e2e71..3df6d0a771 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore.hpp @@ -95,9 +95,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ TYPE current_mem; if constexpr (stage == GenerationStage::ASSIGNMENT) { current_mem = (current_state.memory_size() + 31) / 32; - BOOST_LOG_TRIVIAL(trace) << "\t" - << "Offset = " << std::hex << current_state.stack_top() - << " is_overflow: " << is_overflow << std::dec; } allocate(current_mem, 46, 1); Max_30 new_memory_obj(new_memory_ct, word_size_obj.size, current_mem); @@ -106,9 +103,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ allocate(new_mem, 47, 1); // 4. Calculate proposed operation gas cost - if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) << "\tmemory_size:" << current_mem << " => " << new_mem; - } context_type current_memory_cost_ct = context_object.subcontext({3, 4, 5, 6, 7, 8}, 1, 1); Memory_Cost current_memory_cost_obj(current_memory_cost_ct, current_mem); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore8.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore8.hpp index 27f066664c..608db779de 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore8.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/mstore8.hpp @@ -88,9 +88,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ TYPE current_mem; if constexpr (stage == GenerationStage::ASSIGNMENT) { current_mem = (current_state.memory_size() + 31) / 32; - BOOST_LOG_TRIVIAL(trace) << "\t" - << "Offset = " << std::hex << current_state.stack_top() - << " is_overflow: " << is_overflow << std::dec; } allocate(current_mem, 46, 1); Max_30 new_memory_obj(new_memory_ct, word_size_obj.size, current_mem); @@ -99,9 +96,6 @@ namespace nil::blueprint::bbf::zkevm_small_field{ allocate(new_mem, 47, 1); // 4. Calculate proposed operation gas cost - if constexpr (stage == GenerationStage::ASSIGNMENT) { - BOOST_LOG_TRIVIAL(trace) << "\tmemory_size:" << current_mem << " => " << new_mem; - } context_type current_memory_cost_ct = context_object.subcontext({3, 4, 5, 6, 7, 8}, 1, 1); Memory_Cost current_memory_cost_obj(current_memory_cost_ct, current_mem); diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/pushx.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/pushx.hpp index ac46cbe73d..e7c269b996 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/pushx.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/opcodes/pushx.hpp @@ -80,7 +80,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ for( std::size_t j = 32-x; j < 32; j++){ if( j < 16 ){ tmp = { - TYPE(2), + TYPE(1), current_state.pc(0) + j - (32 - x) + 1, A_bytes[j], TYPE(0), @@ -88,7 +88,7 @@ namespace nil::blueprint::bbf::zkevm_small_field{ }; } else { tmp = { - TYPE(2), + TYPE(1), current_state.pc(1) + j - (32 - x) + 1, A_bytes[j], TYPE(0), diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/tables/bytecode.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/tables/bytecode.hpp index f21e30627b..6e3e671030 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/tables/bytecode.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/small_field/tables/bytecode.hpp @@ -25,132 +25,139 @@ #include -#include +#include "nil/blueprint/zkevm_bbf/types/opcode_enum.hpp" +#include "nil/blueprint/zkevm_bbf/types/hashed_buffers.hpp" namespace nil::blueprint::bbf::zkevm_small_field{ - // Component for bytecode table - - template - class bytecode_table : public generic_component { - using typename generic_component::context_type; - using generic_component::allocate; - using generic_component::copy_constrain; - using generic_component::constrain; - using generic_component::lookup; - using generic_component::lookup_table; - - public: - using typename generic_component::TYPE; - using input_type = std::conditional_t< - stage == GenerationStage::ASSIGNMENT, zkevm_keccak_buffers, std::monostate - >; - - std::size_t max_bytecode_size; // Maximum possible bytecodes sum length - - // interfaces for interaction with other components: - std::vector tag; // Row type: 0(padding), 1 (header), 2 (executable bytes), 3 (metadata) - std::vector index; // Position of the byte within the bytecode - std::vector value; // Byte value (for bytes) or total length (for header) - std::vector is_opcode; // Flags whether the byte is an opcode (1) or not (0) - std::vector bytecode_id; // Bytecode's unique identifier used by zkevm circuit - // We use it to prevent 16-column bytecode hash repeitition - - static std::size_t get_witness_amount(){ - return 5; - } - bytecode_table( +// Component for bytecode table +template +class bytecode_table : public generic_component { + using typename generic_component::context_type; + using generic_component::allocate; + using generic_component::copy_constrain; + using generic_component::constrain; + using generic_component::lookup; + using generic_component::lookup_table; + + public: + using typename generic_component::TYPE; + using input_type = std::conditional_t< + stage == GenerationStage::ASSIGNMENT, zkevm_keccak_buffers, std::monostate + >; + + size_t max_bytecode_size; // Maximum possible bytecodes sum length + + // interfaces for interaction with other components: + std::vector tag; // Row type: 0(padding), 1 (header), 2 (executable bytes), 3 (metadata) + std::vector index; // Position of the byte within the bytecode + std::vector value; // Byte value (for bytes) or total length (for header) + std::vector is_opcode; // Flags whether the byte is an opcode (1) or not (0) + std::vector bytecode_id; // Bytecode's unique identifier used by zkevm circuit + // We use it to prevent 16-column bytecode hash repeitition + + static size_t get_witness_amount() { + return 5; + } + + bytecode_table( context_type &context_object, const input_type &input, - std::size_t max_bytecode_size_ - ) : - max_bytecode_size(max_bytecode_size_), - tag(max_bytecode_size_), - index(max_bytecode_size_), - value(max_bytecode_size_), - is_opcode(max_bytecode_size_), - bytecode_id(max_bytecode_size_), - generic_component(context_object) { - BOOST_LOG_TRIVIAL(trace) << "Small field bytecode table assignment"; - - // If we're in assignment stage, prepare all the values - if constexpr (stage == GenerationStage::ASSIGNMENT) { - const auto &bytecodes = input.get_data(); - std::size_t cur = 0; - - for(std::size_t i = 0; i < bytecodes.size(); i++) { - TYPE push_size = 0; - std::size_t meta_len = 0; - const auto &buffer = bytecodes[i].first; - std::size_t total_len = buffer.size(); - - // Determine the boundary between executable bytes and metadata - std::size_t exec_boundary = total_len; // Default: all bytes are executable - if (total_len >= 2) { - // Metadata length is encoded in the last two bytes - meta_len = (buffer[total_len - 2] << 8) + buffer[total_len - 1]; - if (meta_len + 2 <= total_len) { - std::size_t boundary = total_len - meta_len - 2 - 1; // Byte before metadata - // Check for stopping opcodes (STOP, INVALID, RETURN) that will - // confirm the length of the metadata - if (boundary < total_len && - (buffer[boundary] == 0x00 || buffer[boundary] == 0xfe || - buffer[boundary] == 0xf3) - ) { - exec_boundary = boundary + 1; // Set boundary after the stopping opcode - } - } - } - BOOST_LOG_TRIVIAL(trace) << "Bytecode " << i << " size = " << total_len; - BOOST_LOG_TRIVIAL(trace) << "Executable bytes boundary: " << exec_boundary; - - // Header - BOOST_ASSERT(cur < max_bytecode_size); - tag[cur] = 1; - index[cur] = 0; - value[cur] = total_len; - is_opcode[cur] = 0; - bytecode_id[cur] = i + 1; - cur++; - - // Bytes - for(std::size_t j = 0; j < buffer.size(); j++, cur++){ - BOOST_ASSERT(cur < max_bytecode_size); - auto byte = buffer[j]; - value[cur] = byte; - index[cur] = j; - bytecode_id[cur] = i + 1; - if (j < exec_boundary) { - tag[cur] = 2; - if (push_size == 0) { - is_opcode[cur] = 1; - // Check for PUSH opcodes (0x60 to 0x7f) and set push_size - if (byte > 0x5f && byte < 0x80) { - push_size = byte - 0x5f; - } - } else { // In a PUSH operation - is_opcode[cur] = 0; - push_size--; - } - } else { // Metadata bytes - tag[cur] = 3; - is_opcode[cur] = 0; - push_size = 0; - } + size_t max_bytecode_size_) + : max_bytecode_size(max_bytecode_size_), + tag(max_bytecode_size_), + index(max_bytecode_size_), + value(max_bytecode_size_), + is_opcode(max_bytecode_size_), + bytecode_id(max_bytecode_size_), + generic_component(context_object) { + + if constexpr (stage == GenerationStage::ASSIGNMENT) { + const auto &bytecodes = input.get_data(); + + size_t row = 1; + size_t current_index = 0; + size_t push_size = 0; + + for (size_t i = 0; i < bytecodes.size(); ++i) { + const auto &buffer = bytecodes[i].first; + BOOST_ASSERT(row + 1 + buffer.size() < max_bytecode_size); + + // Header + tag[row] = 0; + index[row] = current_index = 0; + value[row] = buffer.size(); + is_opcode[row] = 0; + bytecode_id[row] = i + 1; + ++row; + + size_t push_size = 0; + while (current_index < buffer.size()) { + auto byte = buffer[current_index]; + value[row] = byte; + index[row] = current_index;; + bytecode_id[row] = i + 1; + tag[row] = 1; + + if (push_size == 0) { + is_opcode[row] = 1; + + // Check for PUSH opcodes (0x60 to 0x7f) and set push_size + if (byte >= 0x60 && byte <= 0x7f) + push_size = byte - 0x5f; + } else { // In a PUSH operation + is_opcode[row] = 0; + --push_size; } + + ++current_index, ++row; } + + // Add potentially accessed implicit zero bytes + BOOST_ASSERT(row + push_size + 1 < max_bytecode_size); + + while (push_size > 0) { // missing push arguments + BOOST_ASSERT(row < max_bytecode_size); + tag[row] = 1; + index[row] = current_index; + value[row] = 0; + is_opcode[row] = 0; + bytecode_id[row] = i + 1; + + ++current_index, ++row, --push_size; + } + + // Add implicit STOP instruction + tag[row] = 1; + index[row] = current_index; + value[row] = 0; + is_opcode[row] = 1; + bytecode_id[row] = i + 1; + ++current_index, ++row; } - // allocate everything. NB: this replaces the map from the original component - for(std::size_t i = 0; i < max_bytecode_size; i++) { - allocate(tag[i], 0, i); - allocate(index[i], 1, i); - allocate(value[i], 2, i); - allocate(is_opcode[i], 3, i); - allocate(bytecode_id[i], 4, i); + + while (row < max_bytecode_size) { + tag[row] = 1; + index[row] = current_index; + value[row] = 0; + is_opcode[row] = 1; + bytecode_id[row] = bytecodes.size(); + ++current_index, ++row; } - // declare dynamic lookup table - lookup_table("zkevm_bytecode",std::vector({0,1,2,3,4}), 0, max_bytecode_size); - lookup_table("zkevm_bytecode_copy",std::vector({1,2,4}), 0, max_bytecode_size); - }; + } + + // allocate everything. NB: this replaces the map from the original component + for (size_t i = 0; i < max_bytecode_size; ++i) { + allocate(tag[i], 0, i); + allocate(index[i], 1, i); + allocate(value[i], 2, i); + allocate(is_opcode[i], 3, i); + allocate(bytecode_id[i], 4, i); + } + + lookup_table("zkevm_bytecode", {0,1,2,3,4}, 0, max_bytecode_size); + lookup_table("zkevm_bytecode_copy", {1,2,4}, 0, max_bytecode_size); }; -} \ No newline at end of file +}; + +} diff --git a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/types/opcode_enum.hpp b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/types/opcode_enum.hpp index abaf9b9db0..5dd2166b88 100644 --- a/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/types/opcode_enum.hpp +++ b/crypto3/libs/blueprint/include/nil/blueprint/zkevm_bbf/types/opcode_enum.hpp @@ -528,9 +528,16 @@ namespace nil { if( number == 0x106 ) return zkevm_opcode::end_call; // opcode for end call if( number == 0x107 ) return zkevm_opcode::end_transaction; // opcode for end call if( number == 0x108 ) return zkevm_opcode::end_block; // opcode for end call - std::cout << "Unknown opcode " << std::hex << number << std::dec << std::endl; - BOOST_ASSERT(false); - return zkevm_opcode::padding; + return zkevm_opcode(-1); + } + + bool is_known_opcode_number(size_t opcode) { + switch (opcode) { + #define ENUM_DEF(name) case zkevm_opcode::name: return true; + ZKEVM_OPCODE_ENUM(ENUM_DEF) + #undef ENUM_DEF + default: return false; + } } zkevm_opcode opcode_from_str(const std::string &str){ @@ -571,6 +578,8 @@ namespace nil { #undef ENUM_DEF return result; } + + #undef ZKEVM_OPCODE_ENUM } // namespace bbf } // namespace blueprint } // namespace nil diff --git a/crypto3/libs/blueprint/test/zkevm_bbf/bytecode.cpp b/crypto3/libs/blueprint/test/zkevm_bbf/bytecode.cpp index b193edc749..443a084a4c 100644 --- a/crypto3/libs/blueprint/test/zkevm_bbf/bytecode.cpp +++ b/crypto3/libs/blueprint/test/zkevm_bbf/bytecode.cpp @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(not_hashed){ test_small_zkevm_bytecode(input, keccak_input, 5000, 50, false); } -BOOST_AUTO_TEST_CASE(new_error, *boost::unit_test::disabled()){ +BOOST_AUTO_TEST_CASE(new_error) { nil::blueprint::bbf::zkevm_keccak_buffers input; std::string bytecode2 = "0x608060405234801561000f575f80fd5b5060043610610029575f3560e01c806364b3cfe61461002d575b5f80fd5b610047600480360381019061004291906100d6565b61005d565b6040516100549190610110565b60405180910390f35b5f8060405180606001604052806029815260200161019e6029913990505f835190505f8081548092919061009090610156565b91905055508092505050919050565b5f80fd5b5f819050919050565b6100b5816100a3565b81146100bf575f80fd5b50565b5f813590506100d0816100ac565b92915050565b5f602082840312156100eb576100ea61009f565b5b5f6100f8848285016100c2565b91505092915050565b61010a816100a3565b82525050565b5f6020820190506101235f830184610101565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610160826100a3565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361019257610191610129565b5b60018201905091905056fe112233445566778899ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; diff --git a/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/r1cs/r1cs.hpp b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/r1cs/r1cs.hpp new file mode 100644 index 0000000000..e2e74c590c --- /dev/null +++ b/crypto3/libs/zk/include/nil/crypto3/zk/snark/arithmetization/r1cs/r1cs.hpp @@ -0,0 +1,141 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2025 Elena Tatuzova +// +// 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 + +/** R1CS constraint system classes */ + +namespace nil::crypto3::zk::r1cs { + + template + struct r1cs_constraint_system { + using field_type = FieldType; + using value_type = typename FieldType::value_type; + using compact_vector_type = std::map; + + // 0-th item in A,B,C corresponds to the constant term + // Standard A,B,C + struct r1cs_constraint { + compact_vector_type A; // Quadratic part variables coefficients + compact_vector_type B; // Quadratic part variables coefficients + compact_vector_type C; // Linear part variables coefficients + + bool is_quadratically_symmetric() const { + BOOST_ASSERT(A.size() == B.size()); + + for( std::size_t i = 1; i < A.size(); i++ ){ + if( A.at(i) != B.at(i) ){ + return false; + } + } + return true; + } + }; + + using constraints_container_type = std::vector; + + r1cs_constraint_system (): + _num_variables(0), + _num_constraints(0) { + } + + r1cs_constraint_system ( + const constraints_container_type &constraints, + std::size_t num_variables + ): + _constraints(constraints), + _num_variables(num_variables), + _num_constraints(constraints.size()) + { + for( const auto &constraint : constraints ){ + for( const auto &item : constraint.A ){ + if( item.first >= _num_variables ){ + _num_variables = item.first; + } + } + for( const auto &item : constraint.B ){ + if( item.first >= _num_variables ){ + _num_variables = item.first; + } + } + for( const auto &item : constraint.C ){ + if( item.first >= _num_variables ){ + _num_variables = item.first; + } + } + } + } + + // Variables are indexed from 1 to n, 0 is reserved for the constant term + bool satisfiability_check(const std::vector &assignment) const { + assert( assignment.size() >= _num_variables ); + std::size_t i = 0; + + // Variables numeration starts from 1, but in vector it starts from 0 + for( const auto &constraint : _constraints ){ + value_type a = constraint.A.contains(0) ? constraint.A.at(0) : 0; + for( const auto &item : constraint.A ){ + if( item.first == 0 ) continue; + a += assignment[item.first-1] * item.second; + } + + value_type b = constraint.B.contains(0) ? constraint.B.at(0) : 0; + for( const auto &item : constraint.B ){ + if( item.first == 0 ) continue; + b += assignment[item.first-1] * item.second; + } + + value_type c = constraint.C.contains(0) ? constraint.C.at(0) : 0; + for( const auto &item : constraint.C ){ + if( item.first == 0 ) continue; + c += assignment[item.first-1] * item.second; + } + + if( a * b != c ){ + BOOST_LOG_TRIVIAL(debug) << "R1CS constraint " << i << " not satisfied: " + << a << " * " << b << " != " << c; + return false; + } + i++; + } + return true; + } + + bool projective_safety_symmetric_check() const { + return true; + } + + protected: + constraints_container_type _constraints; + std::size_t _num_variables; + std::size_t _num_constraints; + }; +} \ No newline at end of file diff --git a/crypto3/libs/zk/test/CMakeLists.txt b/crypto3/libs/zk/test/CMakeLists.txt index c6cdf2f2e9..657dbf9622 100644 --- a/crypto3/libs/zk/test/CMakeLists.txt +++ b/crypto3/libs/zk/test/CMakeLists.txt @@ -75,6 +75,7 @@ set(TESTS_NAMES "systems/plonk/placeholder/placeholder_hashes" "systems/plonk/placeholder/placeholder_curves" "systems/plonk/placeholder/placeholder_quotient_polynomial_chunks" + "systems/r1cs" "transcript/transcript" diff --git a/crypto3/libs/zk/test/systems/r1cs.cpp b/crypto3/libs/zk/test/systems/r1cs.cpp new file mode 100644 index 0000000000..9aa8cddd3f --- /dev/null +++ b/crypto3/libs/zk/test/systems/r1cs.cpp @@ -0,0 +1,98 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 Elena Tatuzova +// +// 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_constraint_test + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include + + +BOOST_GLOBAL_FIXTURE(ExtendedLogFixture); +BOOST_AUTO_TEST_SUITE(r1cs_test_suite) + using FieldType = typename nil::crypto3::algebra::curves::alt_bn128_254::scalar_field_type; + using value = typename FieldType::value_type; + using constraints_container = typename nil::crypto3::zk::r1cs::r1cs_constraint_system::constraints_container_type; + using constraint_type = typename nil::crypto3::zk::r1cs::r1cs_constraint_system::r1cs_constraint; + using constraint_system_type = typename nil::crypto3::zk::r1cs::r1cs_constraint_system; + +BOOST_AUTO_TEST_CASE(basic_test) { + // (1 + x1) = x2 + constraint_type constraint; + constraint.A[0] = 1; + constraint.A[1] = 1; + constraint.B[0] = 1; + constraint.C[2] = 1; + + // (1 + x1) * (1 + x2) = x3 + constraint_type constraint1; + constraint1.A[0] = 1; + constraint1.A[1] = 1; + constraint1.B[0] = 1; + constraint1.B[2] = 1; + constraint1.C[3] = 1; + + // 2 * 1 = 1 + constraint_type constraint2; + constraint2.A[0] = 2; + constraint2.B[0] = value(1) / value(2); + constraint2.C[0] = 1; + + constraint_system_type r1cs_system({constraint, constraint1, constraint2}, 3); + BOOST_CHECK(r1cs_system.satisfiability_check({0, 1, 2})); + BOOST_CHECK(r1cs_system.satisfiability_check({1, 2, 6})); + BOOST_CHECK(!r1cs_system.satisfiability_check({1, 2, 5})); +} + +BOOST_AUTO_TEST_CASE(quadratic_symmetry_test) { + // symmetric constraint: (3 +x1) * (2 + x1) = x_1 + constraint_type symmetric_constraint; + symmetric_constraint.A[0] = 3; + symmetric_constraint.A[1] = 1; + symmetric_constraint.B[0] = 2; + symmetric_constraint.B[1] = 1; + symmetric_constraint.C[2] = 1; + BOOST_CHECK(symmetric_constraint.is_quadratically_symmetric()); + + // non-symmetric constraint: (1 + 3 * x1) * (1 + 2 * x1) = x3 + constraint_type non_symmetric_constraint; + non_symmetric_constraint.A[0] = 1; + non_symmetric_constraint.A[1] = 3; + non_symmetric_constraint.B[0] = 1; + non_symmetric_constraint.B[1] = 2; + non_symmetric_constraint.C[3] = 1; + BOOST_CHECK(!non_symmetric_constraint.is_quadratically_symmetric()); +} +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/crypto3/test_tools/include/nil/crypto3/test_tools/extended_log_fixture.hpp b/crypto3/test_tools/include/nil/crypto3/test_tools/extended_log_fixture.hpp new file mode 100644 index 0000000000..7eabbb61b3 --- /dev/null +++ b/crypto3/test_tools/include/nil/crypto3/test_tools/extended_log_fixture.hpp @@ -0,0 +1,117 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 Elena Tatuzova +// +// 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 +#include + + +// Log-related classes +void file_formatter(boost::log::record_view const& rec, boost::log::formatting_ostream& strm){ + // Finally, put the record message to the stream + if( rec[boost::log::trivial::severity] > boost::log::trivial::info) { + strm << "[" << rec[boost::log::trivial::severity] << "] " << rec[boost::log::expressions::smessage]; + } else { + strm << rec[boost::log::expressions::smessage]; + } +} + + +void colored_formatter(boost::log::record_view const& rec, boost::log::formatting_ostream& strm){ + // Colored output looks nice in terminal, but not in files. + // Use --color-log to enable colored output in terminal. + if( rec[boost::log::trivial::severity] == boost::log::trivial::fatal) { + strm << "[\x1B[91m" << rec[boost::log::trivial::severity] << "\x1B[0m] " << rec[boost::log::expressions::smessage]; + } else if( rec[boost::log::trivial::severity] == boost::log::trivial::error) { + strm << "[\x1B[38;2;255;165;0m" << rec[boost::log::trivial::severity] << "\x1B[0m] " << rec[boost::log::expressions::smessage]; + } else if( rec[boost::log::trivial::severity] == boost::log::trivial::warning) { + strm << "[\x1B[33m" << rec[boost::log::trivial::severity] << "\x1B[0m] " << rec[boost::log::expressions::smessage]; + } else if( rec[boost::log::trivial::severity] == boost::log::trivial::info) { + strm << "[\x1B[32m" << rec[boost::log::trivial::severity] << "\x1B[0m] " << rec[boost::log::expressions::smessage]; + } else { + strm << rec[boost::log::expressions::smessage]; + } +} + + +class ExtendedLogFixture { +public: + ExtendedLogFixture() { + // Initialize the logging system + boost::log::trivial::severity_level log_level = boost::log::trivial::info; + bool is_color = false; + + std::size_t argc = boost::unit_test::framework::master_test_suite().argc; + auto &argv = boost::unit_test::framework::master_test_suite().argv; + for( std::size_t i = 0; i < argc; i++ ){ + std::string arg(argv[i]); + if( arg == "--log-level=trace"){ + log_level = boost::log::trivial::trace; + } + if( arg == "--log-level=debug"){ + log_level = boost::log::trivial::debug; + } + if( arg == "--log-level=info"){ + log_level = boost::log::trivial::info; + } + if( arg == "--log-level=warning"){ + log_level = boost::log::trivial::warning; + } + if( arg == "--log-level=error"){ + log_level = boost::log::trivial::error; + } + if( arg == "--no-log" ){ + log_level = boost::log::trivial::fatal; + } + if( arg == "--color-log" ){ + is_color = true; + } + } + + typedef boost::log::sinks::synchronous_sink< boost::log::sinks::text_ostream_backend > text_sink; + boost::shared_ptr< text_sink > sink = boost::make_shared< text_sink >(); + + sink->locked_backend()->add_stream(boost::shared_ptr< std::ostream >(&std::cout, boost::null_deleter())); + if (is_color) { + sink->set_formatter(&colored_formatter); + } else { + sink->set_formatter(&file_formatter); + } + sink->locked_backend()->auto_flush(true); + boost::log::core::get()->add_sink(sink); + + sink->set_filter( + boost::log::trivial::severity >= log_level + ); + } +}; \ No newline at end of file