diff --git a/.gitmodules b/.gitmodules
index db7cb32d..9076ca5f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,9 +7,6 @@
 [submodule "libs/threaded-math"]
 	path = libs/parallel-math
 	url = git@github.com:NilFoundation/actor-math
-[submodule "libs/threaded-containers"]
-	path = libs/parallel-containers
-	url = git@github.com:NilFoundation/actor-containers
 [submodule "cmake/modules"]
 	path = cmake/modules
 	url = git@github.com:BoostCMake/cmake_modules.git
diff --git a/libs/parallel-containers b/libs/parallel-containers
deleted file mode 160000
index 39e2c812..00000000
--- a/libs/parallel-containers
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 39e2c812d375e8bcb662edd6661a8c26c50eb723
diff --git a/libs/parallel-containers/CMakeLists.txt b/libs/parallel-containers/CMakeLists.txt
new file mode 100644
index 00000000..a8459386
--- /dev/null
+++ b/libs/parallel-containers/CMakeLists.txt
@@ -0,0 +1,98 @@
+#---------------------------------------------------------------------------//
+#  MIT License
+#
+#  Copyright (c) 2020 Mikhail Komarov <nemo@nil.foundation>
+#  Copyright (c) 2021 Aleksei Moskvin <alalmoskvin@gmail.com>
+#
+#  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.
+#---------------------------------------------------------------------------//
+
+cmake_minimum_required(VERSION 2.8.12)
+
+cmake_policy(SET CMP0042 NEW)
+cmake_policy(SET CMP0028 NEW)
+cmake_policy(SET CMP0048 NEW)
+cmake_policy(SET CMP0057 NEW)
+cmake_policy(SET CMP0079 OLD)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake"
+        "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/share/modules/cmake")
+
+include(CMConfig)
+include(CMSetupVersion)
+
+cm_workspace(actor)
+
+include(CMDeploy)
+include(CMSetupVersion)
+
+if (NOT Boost_FOUND AND NOT CMAKE_CROSSCOMPILING)
+    find_package(Boost COMPONENTS REQUIRED filesystem)
+endif ()
+
+cm_project(containers WORKSPACE_NAME ${CMAKE_WORKSPACE_NAME})
+
+option(BUILD_DOXYGEN_DOCS "Build with configuring Doxygen documentation compiler" TRUE)
+
+set(DOXYGEN_OUTPUT_DIR "${CMAKE_CURRENT_LIST_DIR}/docs" CACHE STRING "Specify doxygen output directory")
+
+list(APPEND ${CURRENT_PROJECT_NAME}_PUBLIC_HEADERS
+        )
+
+list(APPEND ${CURRENT_PROJECT_NAME}_UNGROUPED_SOURCES)
+
+list(APPEND ${CURRENT_PROJECT_NAME}_HEADERS
+        ${${CURRENT_PROJECT_NAME}_PUBLIC_HEADERS}
+        ${${CURRENT_PROJECT_NAME}_UNGROUPED_SOURCES})
+
+list(APPEND ${CURRENT_PROJECT_NAME}_SOURCES
+        ${${CURRENT_PROJECT_NAME}_UNGROUPED_SOURCES})
+
+cm_setup_version(VERSION 0.1.0 PREFIX ${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME})
+
+add_library(${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME} INTERFACE)
+
+set_target_properties(${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME} PROPERTIES
+        EXPORT_NAME ${CURRENT_PROJECT_NAME})
+
+target_link_libraries(${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME} INTERFACE
+
+        ${CMAKE_WORKSPACE_NAME}::core
+
+        crypto3::algebra
+        crypto3::hash
+
+        ${Boost_LIBRARIES})
+
+target_include_directories(${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME} INTERFACE
+        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+        "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>"
+
+        ${Boost_INCLUDE_DIRS})
+
+cm_deploy(TARGETS ${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME} INCLUDE include NAMESPACE ${CMAKE_WORKSPACE_NAME}::)
+
+if (BUILD_TESTS)
+    add_subdirectory(test)
+endif ()
+
+if (BUILD_EXAMPLES)
+    add_subdirectory(example)
+endif ()
+
diff --git a/libs/parallel-containers/README.md b/libs/parallel-containers/README.md
new file mode 100644
index 00000000..ddccd889
--- /dev/null
+++ b/libs/parallel-containers/README.md
@@ -0,0 +1,21 @@
+# Containers for =nil; Foundation's Cryptography Suite
+
+Containers using =nil; Foundation's cryptography suite.
+
+## Building
+
+This library uses Boost CMake build modules (https://github.com/BoostCMake/cmake_modules.git).
+To actually include this library in a project it is required to:
+
+1. Add [CMake Modules](https://github.com/BoostCMake/cmake_modules.git) as submodule to target project repository.
+2. Add all the internal dependencies using [CMake Modules](https://github.com/BoostCMake/cmake_modules.git) as submodules to target project repository.
+3. Initialize parent project with [CMake Modules](https://github.com/BoostCMake/cmake_modules.git) (Look at [crypto3](https://github.com/nilfoundation/crypto3.git) for the example)
+
+## Dependencies
+
+### Internal
+
+* [Hash](https://github.com/nilfoundation/crypto3-hash.git).
+
+### External
+* [Boost](https://boost.org) (>= 1.76)
diff --git a/libs/parallel-containers/example/CMakeLists.txt b/libs/parallel-containers/example/CMakeLists.txt
new file mode 100644
index 00000000..34657548
--- /dev/null
+++ b/libs/parallel-containers/example/CMakeLists.txt
@@ -0,0 +1,48 @@
+#---------------------------------------------------------------------------//
+#  MIT License
+#
+#  Copyright (c) 2020 Mikhail Komarov <nemo@nil.foundation>
+#
+#  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_directories("${CMAKE_CURRENT_SOURCE_DIR}/../include"
+        "${CMAKE_CURRENT_BINARY_DIR}/include"
+
+        ${Boost_INCLUDE_DIRS})
+
+macro(define_containers_example example)
+    get_filename_component(test_name ${example} NAME)
+    set(target_name ${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME}_${test_name}_example)
+
+    add_executable(${target_name} ${example}.cpp)
+    target_link_libraries(${target_name} PRIVATE
+            ${CMAKE_WORKSPACE_NAME}::algebra
+            ${CMAKE_WORKSPACE_NAME}::hash
+            ${Boost_LIBRARIES})
+    set_target_properties(${target_name} PROPERTIES CXX_STANDARD 17)
+endmacro()
+
+set(EXAMPLES_NAMES
+    "merkle/merkle")
+
+foreach(EXAMPLE_NAME ${EXAMPLES_NAMES})
+    define_containers_example(${EXAMPLE_NAME})
+endforeach()
diff --git a/libs/parallel-containers/example/merkle/merkle.cpp b/libs/parallel-containers/example/merkle/merkle.cpp
new file mode 100644
index 00000000..cc7e6003
--- /dev/null
+++ b/libs/parallel-containers/example/merkle/merkle.cpp
@@ -0,0 +1,67 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2021-2022 Aleksei Moskvin <alalmoskvin@gmail.com>
+//
+// 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/crypto3/hash/sha2.hpp>
+#include <nil/crypto3/hash/md5.hpp>
+#include <nil/crypto3/hash/blake2b.hpp>
+
+#include <nil/crypto3/container/merkle/tree.hpp>
+#include <nil/crypto3/container/merkle/proof.hpp>
+
+using namespace nil::crypto3;
+using namespace nil::crypto3::containers;
+
+int main() {
+    std::vector<std::array<char, 1> > data_on_leafs = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}};
+    std::array<char, 1> element_not_in_tree = {'9'};
+    merkle_tree<hashes::blake2b<224>, 3> tree = make_merkle_tree<hashes::blake2b<224>, 3>(data_on_leafs.begin(), data_on_leafs.end());
+    merkle_proof<hashes::blake2b<224>, 3> proof_leaf_3(tree, 3);
+    merkle_proof<hashes::blake2b<224>, 3> proof_leaf_0(tree, 0);
+//    std::cout << "Tree structure:" << std::endl;
+//    std::cout << tree << std::endl;
+    std::vector<std::array<char, 1>> data_to_check = {{data_on_leafs[2]}, {data_on_leafs[0]}, element_not_in_tree};
+    for (size_t i = 0; i < data_to_check.size(); ++i) {
+        std::cout << "Is leaf " << data_to_check[i][0] << " was in tree in position 0: ";
+        std::cout << std::boolalpha << proof_leaf_0.validate(data_to_check[i]) << std::endl;
+        std::cout << "Is leaf " << data_to_check[i][0] << " was in tree in position 3: ";
+        std::cout << std::boolalpha << proof_leaf_3.validate(data_to_check[i]) << std::endl;
+    }
+    std::cout << std::endl;
+
+    std::array<char, 7> left = {'\x6d', '\x65', '\x73', '\x73', '\x61', '\x67', '\x65'};
+    std::array<char, 7> right = {'\x20', '\x64', '\x69', '\x67', '\x65', '\x73', '\x74'};
+    std::vector<std::array<char, 7> > simple_binary_tree_data = {left, right};
+    merkle_tree<hashes::blake2b<224>, 2> simple_binary_tree = make_merkle_tree<hashes::blake2b<224>, 2>(simple_binary_tree_data.begin(), simple_binary_tree_data.end());
+    merkle_proof<hashes::blake2b<224>, 2> simple_binary_proof_leaf_1(simple_binary_tree, 1);
+//    std::cout << "Tree simple binary structure:" << std::endl;
+//    std::cout << simple_binary_tree << std::endl;
+    std::cout << "Is leaf " << data_on_leafs[1][0] << " was in tree in position 1: ";
+    std::cout << std::boolalpha << simple_binary_proof_leaf_1.validate(data_on_leafs[1]) << std::endl;
+    std::cout << "Is leaf left was in tree in position 1: ";
+    std::cout << std::boolalpha << simple_binary_proof_leaf_1.validate(left) << std::endl;
+    std::cout << "Is leaf right was in tree in position 1: ";
+    std::cout << std::boolalpha << simple_binary_proof_leaf_1.validate(right) << std::endl;
+
+}
\ No newline at end of file
diff --git a/libs/parallel-containers/include/nil/crypto3/container/accumulation_vector.hpp b/libs/parallel-containers/include/nil/crypto3/container/accumulation_vector.hpp
new file mode 100644
index 00000000..f2d48320
--- /dev/null
+++ b/libs/parallel-containers/include/nil/crypto3/container/accumulation_vector.hpp
@@ -0,0 +1,104 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
+// Copyright (c) 2021 Ilias Khairullin <ilias@nil.foundation>
+//
+// 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.
+//---------------------------------------------------------------------------//
+
+#ifndef CRYPTO3_ZK_SNARK_ACCUMULATION_VECTOR_HPP
+#define CRYPTO3_ZK_SNARK_ACCUMULATION_VECTOR_HPP
+
+#include <iostream>
+#include <iterator>
+
+#include <nil/crypto3/container/sparse_vector.hpp>
+
+namespace nil {
+    namespace crypto3 {
+        namespace container {
+
+            /**
+             * An accumulation vector comprises an accumulation value and a sparse vector.
+             * The method "accumulate_chunk" allows one to accumulate portions of the sparse
+             * vector into the accumulation value.
+             */
+            template<typename Type>
+            class accumulation_vector {
+                using underlying_value_type = typename Type::value_type;
+
+            public:
+                using group_type = Type;
+
+                underlying_value_type first;
+                sparse_vector<Type> rest;
+
+                accumulation_vector() = default;
+                accumulation_vector(const accumulation_vector<Type> &other) = default;
+                accumulation_vector(accumulation_vector<Type> &&other) = default;
+                accumulation_vector(const underlying_value_type &first, sparse_vector<Type> &&rest) :
+                    first(first), rest(std::move(rest)) {};
+                accumulation_vector(underlying_value_type &&first, sparse_vector<Type> &&rest) :
+                    first(std::move(first)), rest(std::move(rest)) {};
+                accumulation_vector(underlying_value_type &&first, std::vector<underlying_value_type> &&v) :
+                    first(std::move(first)), rest(std::move(v)) {
+                }
+                accumulation_vector(std::vector<underlying_value_type> &&v) :
+                    first(underlying_value_type::zero()), rest(std::move(v)) {};
+
+                accumulation_vector<Type> &operator=(const accumulation_vector<Type> &other) = default;
+                accumulation_vector<Type> &operator=(accumulation_vector<Type> &&other) = default;
+
+                bool operator==(const accumulation_vector<Type> &other) const {
+                    return (this->first == other.first && this->rest == other.rest);
+                }
+
+                bool is_fully_accumulated() const {
+                    return rest.empty();
+                }
+
+                std::size_t domain_size() const {
+                    return rest.domain_size();
+                }
+
+                std::size_t size() const {
+                    return rest.domain_size();
+                }
+
+                std::size_t size_in_bits() const {
+                    const std::size_t first_size_in_bits = Type::value_bits;
+                    const std::size_t rest_size_in_bits = rest.size_in_bits();
+                    return first_size_in_bits + rest_size_in_bits;
+                }
+
+                template<typename InputIterator>
+                accumulation_vector<Type> accumulate_chunk(InputIterator begin, InputIterator end,
+                                                           std::size_t offset) const {
+                    std::pair<underlying_value_type, sparse_vector<Type>> acc_result = rest.insert(offset, begin, end);
+                    underlying_value_type new_first = first + acc_result.first;
+                    return accumulation_vector<Type>(std::move(new_first), std::move(acc_result.second));
+                }
+            };
+        }    // namespace container
+    }        // namespace crypto3
+}    // namespace nil
+
+#endif    // CRYPTO3_ZK_SNARK_ACCUMULATION_VECTOR_HPP
diff --git a/libs/parallel-containers/include/nil/crypto3/container/merkle/node.hpp b/libs/parallel-containers/include/nil/crypto3/container/merkle/node.hpp
new file mode 100644
index 00000000..66940c9f
--- /dev/null
+++ b/libs/parallel-containers/include/nil/crypto3/container/merkle/node.hpp
@@ -0,0 +1,50 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2021 Aleksei Moskvin <alalmoskvin@gmail.com>
+//
+// 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.
+//---------------------------------------------------------------------------//
+
+#ifndef CRYPTO3_MERKLE_TREE_NODE_HPP
+#define CRYPTO3_MERKLE_TREE_NODE_HPP
+
+#include <numeric>
+
+namespace nil {
+    namespace crypto3 {
+        namespace containers {
+            namespace detail {
+                template<typename Hash>
+                struct merkle_tree_node {
+                    typedef Hash hash_type;
+
+                    constexpr static const std::size_t digest_bits = hash_type::digest_bits;
+                    typedef typename hash_type::digest_type digest_type;
+
+                    typedef typename Hash::digest_type value_type;
+                    constexpr static const std::size_t value_bits = digest_bits;
+                };
+            }    // namespace detail
+        }        // namespace containers
+    }        // namespace crypto3
+}    // namespace nil
+
+#endif    // CRYPTO3_NODE_HPP
diff --git a/libs/parallel-containers/include/nil/crypto3/container/merkle/proof.hpp b/libs/parallel-containers/include/nil/crypto3/container/merkle/proof.hpp
new file mode 100644
index 00000000..12c29ab2
--- /dev/null
+++ b/libs/parallel-containers/include/nil/crypto3/container/merkle/proof.hpp
@@ -0,0 +1,305 @@
+//---------------------------------------------------------------------------//
+//  MIT License
+//
+//  Copyright (c) 2020-2021 Mikhail Komarov <nemo@nil.foundation>
+//  Copyright (c) 2020-2021 Nikita Kaskov <nemo@nil.foundation>
+//  Copyright (c) 2021-2022 Aleksei Moskvin <alalmoskvin@gmail.com>
+//  Copyright (c) 2021 Ilias Khairullin <ilias@nil.foundation>
+//
+//  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.
+//---------------------------------------------------------------------------//
+
+#ifndef CRYPTO3_MERKLE_PROOF_HPP
+#define CRYPTO3_MERKLE_PROOF_HPP
+
+#include <algorithm>
+#include <vector>
+#include <stack>
+
+#include <boost/variant.hpp>
+
+#include <nil/crypto3/hash/type_traits.hpp>
+#include <nil/crypto3/container/merkle/tree.hpp>
+
+namespace nil {
+    namespace crypto3 {
+        namespace zk {
+            namespace components {
+                template<typename, typename, std::size_t>
+                struct merkle_proof;
+            }    // namespace components
+        }        // namespace zk
+        namespace marshalling {
+            namespace types {
+                template<typename, typename>
+                struct merkle_proof_marshalling;
+            }
+        }    // namespace marshalling
+        namespace containers {
+            namespace detail {
+                template<typename NodeType, std::size_t Arity = 2>
+                class merkle_proof_impl {
+                public:
+                    typedef NodeType node_type;
+                    typedef typename node_type::hash_type hash_type;
+
+                    constexpr static const std::size_t arity = Arity;
+
+                    constexpr static const std::size_t value_bits = node_type::value_bits;
+                    typedef typename node_type::value_type value_type;
+
+                    struct path_element_type {
+                        path_element_type(value_type x, size_t pos) : _hash(x), _position(pos) {
+                        }
+                        path_element_type() {
+                        }
+
+                        bool operator==(const path_element_type &rhs) const {
+                            return _hash == rhs._hash && _position == rhs._position;
+                        }
+                        bool operator!=(const path_element_type &rhs) const {
+                            return !(rhs == *this);
+                        }
+
+                        const value_type &hash() const {
+                            return _hash;
+                        }
+
+                        std::size_t position() const {
+                            return _position;
+                        }
+
+                        value_type _hash;
+                        std::size_t _position;
+
+                        template<typename, typename>
+                        friend class nil::crypto3::marshalling::types::merkle_proof_marshalling;
+                    };
+
+                    typedef std::array<path_element_type, Arity - 1> layer_type;
+                    typedef std::vector<layer_type> path_type;
+
+                    merkle_proof_impl() : _li(0) {};
+
+                    merkle_proof_impl(std::size_t li, value_type root, path_type path) : _li(li), _root(root),
+                                                                                         _path(path){};
+
+                    merkle_proof_impl(const merkle_tree<hash_type, arity> &tree, const std::size_t leaf_idx) {
+                        _root = tree.root();
+                        _path.resize(tree.row_count() - 1);
+                        _li = leaf_idx;
+
+                        typename std::vector<layer_type>::iterator v_itr = _path.begin();
+                        std::size_t cur_leaf = leaf_idx;
+                        std::size_t row_len = tree.leaves();
+                        std::size_t row_begin_idx = 0;
+                        while (cur_leaf != tree.size() - 1) {    // while it's not _root
+                            std::size_t cur_leaf_pos = cur_leaf % arity;
+                            std::size_t cur_leaf_arity_pos = (cur_leaf - row_begin_idx) / arity;
+                            std::size_t begin_this_arity = cur_leaf - cur_leaf_pos;
+                            typename layer_type::iterator a_itr = v_itr->begin();
+                            for (size_t i = 0; i < cur_leaf_pos; ++i, ++begin_this_arity, ++a_itr) {
+                                *a_itr = path_element_type(tree[begin_this_arity], i);
+                            }
+                            for (size_t i = cur_leaf_pos + 1; i < arity; ++i, ++begin_this_arity, ++a_itr) {
+                                *a_itr = path_element_type(tree[begin_this_arity + 1], i);
+                            }
+                            v_itr++;
+                            cur_leaf = row_len + row_begin_idx + cur_leaf_arity_pos;
+                            row_begin_idx += row_len;
+                            row_len /= arity;
+                        }
+                    }
+
+                    template<typename Hashable, typename HashType = typename NodeType::hash_type>
+                    bool validate(const Hashable &a) const {
+                        using hash_type = typename NodeType::hash_type;
+                        value_type d = crypto3::hash<hash_type>(a);
+                        for (auto &it : _path) {
+                            accumulator_set<hash_type> acc;
+                            size_t i = 0;
+                            for (; (i < arity - 1) && i == it[i]._position; ++i) {
+                                crypto3::hash<hash_type>(it[i]._hash, acc);
+                            }
+                            crypto3::hash<hash_type>(d, acc);
+                            for (; i < arity - 1; ++i) {
+                                crypto3::hash<hash_type>(it[i]._hash, acc);
+                            }
+                            d = accumulators::extract::hash<hash_type>(acc);
+                        }
+                        return (d == _root);
+                    }
+
+                    static std::vector<merkle_proof_impl>
+                        generate_compressed_proofs(const containers::merkle_tree<NodeType, Arity> &tree,
+                                                    std::vector<std::size_t> leaf_idxs) {
+                        assert(leaf_idxs.size() > 0);
+                        std::vector<std::size_t> sorted_idx(leaf_idxs.size());
+                        std::iota(sorted_idx.begin(), sorted_idx.end(), 0);
+                        std::sort(sorted_idx.begin(), sorted_idx.end(), [&leaf_idxs](std::size_t i, std::size_t j) {
+                                                                        return leaf_idxs[i] < leaf_idxs[j]; });
+                        std::vector<merkle_proof_impl> result_proofs(leaf_idxs.size());
+                        std::size_t row_len = tree.leaves();
+                        std::vector<bool> known(2 * row_len, false);
+                        std::size_t prev_leaf_idx = leaf_idxs[sorted_idx[0]] + 1;
+                        for (auto idx : sorted_idx) {
+                            auto leaf_idx = leaf_idxs[idx];
+                            if (leaf_idx == prev_leaf_idx) {
+                                result_proofs[idx] = merkle_proof_impl(leaf_idx, tree.root(), path_type());
+                                assert(result_proofs[idx].path().size() == 0);
+                                continue;
+                            }
+                            path_type path(tree.row_count() - 1);
+                            typename path_type::iterator path_itr = path.begin();
+                            std::size_t cur_leaf = leaf_idx;
+                            std::size_t row_len = tree.leaves();
+                            std::size_t row_begin_idx = 0;
+                            bool finish_path = false;
+                            while (cur_leaf != tree.size() - 1) {
+                                std::size_t cur_leaf_pos = cur_leaf % Arity;
+                                std::size_t cur_leaf_arity_pos = (cur_leaf - row_begin_idx) / Arity;
+                                std::size_t begin_this_arity = cur_leaf - cur_leaf_pos;
+                                typename layer_type::iterator layer_itr = path_itr->begin();
+                                for (size_t i = 0; i < cur_leaf_pos; ++i, ++begin_this_arity) {
+                                    if (!known[begin_this_arity]) {
+                                        known[begin_this_arity] = true;
+                                    } else {
+                                        finish_path = true;
+                                    }
+                                    *layer_itr = path_element_type(tree[begin_this_arity], i);
+                                    ++layer_itr;
+                                }
+                                for (size_t i = cur_leaf_pos + 1; i < Arity; ++i, ++begin_this_arity) {
+                                    if (!known[begin_this_arity + 1]) {
+                                        known[begin_this_arity + 1] = true;
+                                    } else {
+                                        finish_path = true;
+                                    }
+                                    *layer_itr = path_element_type(tree[begin_this_arity + 1], i);
+                                    ++layer_itr;
+                                }
+                                path_itr++;
+                                if (finish_path) {
+                                    break;
+                                }
+                                cur_leaf = row_len + row_begin_idx + cur_leaf_arity_pos;
+                                row_begin_idx += row_len;
+                                row_len /= Arity;
+                            }
+                            path.resize(path_itr - path.begin());
+                            result_proofs[idx] = merkle_proof_impl(leaf_idx, tree.root(), path);
+                            prev_leaf_idx = leaf_idx;
+                        }
+                        return result_proofs;
+                    }
+
+                    template<typename Hashable>
+                    static bool validate_compressed_proofs(const std::vector<merkle_proof_impl> &proofs,
+                                                            const std::vector<Hashable> &a) {
+                        assert(proofs.size() == a.size());
+                        assert(proofs.size() > 0);
+                        std::vector<std::size_t> sorted_idx(proofs.size());
+                        std::iota(sorted_idx.begin(), sorted_idx.end(), 0);
+                        std::sort(sorted_idx.begin(), sorted_idx.end(), [&proofs](std::size_t i, std::size_t j) {
+                                                                        return proofs[i].leaf_index() >= proofs[j].leaf_index(); });
+                        std::stack<std::pair<value_type, std::size_t>> st;
+                        auto root = proofs[sorted_idx.back()].root();
+                        auto full_proof_size = proofs[sorted_idx.back()].path().size();
+                        for (auto idx : sorted_idx) {
+                            auto path = proofs[idx].path();
+                            value_type d = crypto3::hash<hash_type>(a[idx]);
+                            std::vector<value_type> hashes = {d};
+                            for (auto &it : path) {
+                                accumulator_set<hash_type> acc;
+                                std::size_t i = 0;
+                                for (; (i < Arity - 1) && i == it[i].position(); ++i) {
+                                    crypto3::hash<hash_type>(it[i].hash(), acc);
+                                }
+                                crypto3::hash<hash_type>(d.begin(), d.end(), acc);
+                                for (; i < Arity - 1; ++i) {
+                                    crypto3::hash<hash_type>(it[i].hash(), acc);
+                                }
+                                d = accumulators::extract::hash<hash_type>(acc);
+                                hashes.push_back(d);
+                            }
+                            while (!st.empty()) {
+                                auto top = st.top();
+                                if (top.second >= hashes.size()) {
+                                    break;
+                                }
+                                if (hashes[top.second] == top.first) {
+                                    st.pop();
+                                } else {
+                                    return false;
+                                }
+                            }
+                            if (path.size() < full_proof_size) {
+                                st.push(std::make_pair(d, path.size()));
+                            } else if (d != root) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    }
+
+                    std::size_t leaf_index() const {
+                        return _li;
+                    }
+
+                    bool operator==(const merkle_proof_impl &rhs) const {
+                        return _li == rhs._li && _root == rhs._root && _path == rhs._path;
+                    }
+                    bool operator!=(const merkle_proof_impl &rhs) const {
+                        return !(rhs == *this);
+                    }
+
+                    const value_type &root() const {
+                        return _root;
+                    }
+
+                    const path_type &path() const {
+                        return _path;
+                    }
+
+                private:
+                    std::size_t _li;
+                    value_type _root;
+                    path_type _path;
+
+                    template<typename, typename, std::size_t>
+                    friend class nil::crypto3::zk::components::merkle_proof;
+
+                    template<typename, typename>
+                    friend class nil::crypto3::marshalling::types::merkle_proof_marshalling;
+                };
+
+
+            }    // namespace detail
+
+            template<typename T, std::size_t Arity>
+            using merkle_proof =
+                typename std::conditional<nil::crypto3::detail::is_hash<T>::value,
+                                          detail::merkle_proof_impl<detail::merkle_tree_node<T>, Arity>,
+                                          detail::merkle_proof_impl<T, Arity>>::type;
+
+        }    // namespace containers
+    }        // namespace crypto3
+}    // namespace nil
+
+#endif
diff --git a/libs/parallel-containers/include/nil/crypto3/container/merkle/tree.hpp b/libs/parallel-containers/include/nil/crypto3/container/merkle/tree.hpp
new file mode 100644
index 00000000..ac284a07
--- /dev/null
+++ b/libs/parallel-containers/include/nil/crypto3/container/merkle/tree.hpp
@@ -0,0 +1,532 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2021-2022 Aleksei Moskvin <alalmoskvin@gmail.com>
+// Copyright (c) 2021 Ilias Khairullin <ilias@nil.foundation>
+//
+// 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.
+//---------------------------------------------------------------------------//
+
+#ifndef CRYPTO3_MERKLE_TREE_HPP
+#define CRYPTO3_MERKLE_TREE_HPP
+
+#include <vector>
+#include <cmath>
+
+#include <nil/crypto3/algebra/curves/pallas.hpp>
+
+#include <nil/crypto3/detail/static_digest.hpp>
+#include <nil/crypto3/detail/type_traits.hpp>
+
+#include <nil/crypto3/hash/type_traits.hpp>
+#include <nil/crypto3/hash/algorithm/hash.hpp>
+#include <nil/crypto3/container/merkle/node.hpp>
+
+#include <nil/actor/core/thread_pool.hpp>
+#include <nil/actor/core/parallelization_utils.hpp>
+
+namespace nil {
+    namespace crypto3 {
+        namespace containers {
+            namespace detail {
+                // returns next highest power of two from a given number if it is not
+                // already a power of two.
+                inline size_t next_pow2(size_t n) {
+                    return std::pow(2, std::ceil(std::log(n)));
+                }
+
+                // find power of 2 of a number which is power of 2
+                inline size_t log2_pow2(size_t n) {
+                    return next_pow2(n);
+                }
+
+                // Row_Count calculation given the number of _leaves in the tree and the branches.
+                inline size_t merkle_tree_row_count(size_t leafs, size_t branches) {
+                    // Optimization
+                    if (branches == 2) {
+                        return std::log2(leafs) + 1;
+                    } else {
+                        return round(std::log(leafs) / std::log(branches)) + 1;
+                    }
+                }
+
+                // Tree length calculation given the number of _leaves in the tree and the branches.
+                inline size_t merkle_tree_length(size_t leafs, size_t branches) {
+                    // Optimization
+                    size_t len = leafs;
+                    if (branches == 2) {
+                        len = 2 * leafs - 1;
+                    } else {
+                        size_t cur = leafs;
+                        while (cur != 0) {
+                            cur /= branches;
+                            len += cur;
+                        }
+                    }
+                    return len;
+                }
+
+                // This method returns the number of '_leaves' given a merkle tree
+                // length of 'len', where _leaves must be a power of 2, respecting the
+                // number of branches.
+                inline size_t merkle_tree_leaves(size_t tree_s, size_t branches) {
+                    // Optimization
+                    size_t len = tree_s;
+                    if (branches == 2) {
+                        len = (tree_s + 1) >> 1;
+                    } else {
+                        size_t cur = 1;
+                        while (cur < len) {
+                            len -= cur;
+                            cur *= branches;
+                        }
+                    }
+                    return len;
+                }
+
+                // Tree length calculation given the number of _leaves in the tree, the
+                // rows_to_discard, and the branches.
+                inline size_t merkle_tree_cache_size(size_t leafs, size_t branches, size_t rows_to_discard) {
+                    size_t shift = log2_pow2(branches);
+                    size_t len = merkle_tree_length(leafs, branches);
+                    size_t row_count = merkle_tree_row_count(leafs, branches);
+
+                    // '_rc - 1' means that we start discarding rows above the base
+                    // layer, which is included in the current _rc.
+                    size_t cache_base = row_count - 1 - rows_to_discard;
+
+                    size_t cache_size = len;
+                    size_t cur_leafs = leafs;
+
+                    while (row_count > cache_base) {
+                        cache_size -= cur_leafs;
+                        cur_leafs >>= shift;    // cur /= branches
+                        row_count -= 1;
+                    }
+
+                    return cache_size;
+                }
+
+                inline bool is_merkle_tree_size_valid(size_t leafs, size_t branches) {
+                    if (branches == 0 || leafs != next_pow2(leafs) || branches != next_pow2(branches)) {
+                        return false;
+                    }
+
+                    size_t cur = leafs;
+                    size_t shift = log2_pow2(branches);
+                    while (cur != 1) {
+                        cur >>= shift;    // cur /= branches
+                        if (cur > leafs || cur == 0) {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                }
+
+                // Given a tree of '_rc' with the specified number of 'branches',
+                // calculate the length of _hashes required for the proof.
+                inline size_t merkle_proof_lemma_length(size_t row_count, size_t branches) {
+                    return 2 + ((branches - 1) * (row_count - 1));
+                }
+
+                // Merkle Tree.
+                //
+                // All _leaves and nodes are stored in a BGL graph structure.
+                //
+                // A merkle tree is a tree in which every non-leaf node is the hash of its
+                // child nodes. A diagram for merkle_tree_impl arity = 2:
+                //
+                //         root = h1234 = h(h12 + h34)
+                //       ./                           \.
+                //  h12 = h(h1 + h2)            h34 = h(h3 + h4)
+                //  ./            \.            ./            \.
+                // h1 = h(tx1)  h2 = h(tx2)    h3 = h(tx3)  h4 = h(tx4)
+                // ```
+                //
+                // In graph representation:
+                //
+                // ```text
+                //    root -> h12, h34
+                //    h12  -> h1, h2
+                //    h34  -> h3, h4
+                // ```
+                //
+                // Merkle root is always the top element.
+                template<typename NodeType, size_t Arity = 2>
+                struct merkle_tree_impl {
+                    typedef NodeType node_type;
+
+                    typedef typename node_type::hash_type hash_type;
+
+                    typedef typename node_type::value_type value_type;
+                    constexpr static const std::size_t value_bits = node_type::value_bits;
+
+                    typedef std::vector<value_type> container_type;
+
+                    typedef typename container_type::allocator_type allocator_type;
+                    typedef typename container_type::reference reference;
+                    typedef typename container_type::const_reference const_reference;
+                    typedef typename container_type::size_type size_type;
+                    typedef typename container_type::difference_type difference_type;
+                    typedef typename container_type::pointer pointer;
+                    typedef typename container_type::const_pointer const_pointer;
+                    typedef typename container_type::iterator iterator;
+                    typedef typename container_type::const_iterator const_iterator;
+                    typedef typename container_type::reverse_iterator reverse_iterator;
+                    typedef typename container_type::const_reverse_iterator const_reverse_iterator;
+
+                    merkle_tree_impl() : _size(0), _leaves(0), _rc(0) {};
+
+                    ~merkle_tree_impl() = default;
+
+                    merkle_tree_impl(size_t n) :
+                            _size(detail::merkle_tree_length(n, Arity)), _leaves(n),
+                            _rc(detail::merkle_tree_row_count(n, Arity)) {
+                        BOOST_ASSERT_MSG(pow(Arity, round(std::log(n) / std::log(Arity))) == n,
+                                         "Wrong leaves number, it must be a power of Arity.");
+                    }
+
+                    merkle_tree_impl(const merkle_tree_impl &x) :
+                            _hashes(x._hashes), _size(x._size), _leaves(x._leaves), _rc(x._rc) {
+                    }
+
+                    merkle_tree_impl(const merkle_tree_impl &x, const allocator_type &a) : _hashes(x.hashes(), a),
+                                                                                           _size(x._size),
+                                                                                           _leaves(x._leaves),
+                                                                                           _rc(x._rc) {}
+
+                    merkle_tree_impl(const std::initializer_list<value_type> &il) : _hashes(il) {
+                        set_leaves(detail::merkle_tree_leaves(std::distance(il.begin(), il.end()), Arity));
+                        set_row_count(detail::merkle_tree_row_count(_leaves, Arity));
+                        set_complete_size(detail::merkle_tree_length(_leaves, Arity));
+                    }
+
+                    template<typename Iterator, typename std::enable_if<std::is_same<typename Iterator::value_type, value_type>::value, bool>::type = true>
+                    merkle_tree_impl(Iterator first, Iterator last) : _hashes(first, last) {
+                        set_leaves(detail::merkle_tree_leaves(std::distance(first, last), Arity));
+                        set_row_count(detail::merkle_tree_row_count(_leaves, Arity));
+                        set_complete_size(detail::merkle_tree_length(_leaves, Arity));
+                    }
+
+                    merkle_tree_impl(const std::initializer_list<value_type> &il, const allocator_type &a) : _hashes(il, a) {
+                        set_leaves(detail::merkle_tree_leaves(std::distance(il.begin(), il.end()), Arity));
+                        set_row_count(detail::merkle_tree_row_count(_leaves, Arity));
+                        set_complete_size(detail::merkle_tree_length(_leaves, Arity));
+                    }
+
+                    merkle_tree_impl(merkle_tree_impl &&x)
+                    BOOST_NOEXCEPT(std::is_nothrow_move_constructible<allocator_type>::value):
+                            _hashes(x._hashes),
+                            _size(x._size), _leaves(x._leaves), _rc(x._rc) {
+                    }
+
+                    merkle_tree_impl(merkle_tree_impl &&x, const allocator_type &a) :
+                            _hashes(x.hashes(), a), _size(x._size), _leaves(x._leaves), _rc(x._rc) {
+                    }
+
+                    merkle_tree_impl &operator=(const merkle_tree_impl &x) {
+                        _hashes = x.hashes();
+                        return *this;
+                    }
+
+                    merkle_tree_impl &operator=(merkle_tree_impl &&x) {
+                        _hashes = x._hashes;
+                        _size = x._size;
+                        _leaves = x._leaves;
+                        _rc = x._rc;
+                        return *this;
+                    }
+
+                    bool operator==(const merkle_tree_impl &rhs) const {
+                        return _hashes == rhs.val;
+                    }
+
+                    bool operator!=(const merkle_tree_impl &rhs) const {
+                        return !(rhs == *this);
+                    }
+
+                    allocator_type get_allocator() const BOOST_NOEXCEPT {
+                        return this->val.__alloc();
+                    }
+
+                    iterator begin() BOOST_NOEXCEPT {
+                        return _hashes.begin();
+                    }
+
+                    const_iterator begin() const BOOST_NOEXCEPT {
+                        return _hashes.begin();
+                    }
+
+                    iterator end() BOOST_NOEXCEPT {
+                        return _hashes.end();
+                    }
+
+                    const_iterator end() const BOOST_NOEXCEPT {
+                        return _hashes.end();
+                    }
+
+                    reverse_iterator rbegin() BOOST_NOEXCEPT {
+                        return _hashes.rbegin();
+                    }
+
+                    const_reverse_iterator rbegin() const BOOST_NOEXCEPT {
+                        return _hashes.rbegin();
+                    }
+
+                    reverse_iterator rend() BOOST_NOEXCEPT {
+                        return reverse_iterator(begin());
+                    }
+
+                    const_reverse_iterator rend() const BOOST_NOEXCEPT {
+                        return const_reverse_iterator(begin());
+                    }
+
+                    const_iterator cbegin() const BOOST_NOEXCEPT {
+                        return begin();
+                    }
+
+                    const_iterator cend() const BOOST_NOEXCEPT {
+                        return end();
+                    }
+
+                    const_reverse_iterator crbegin() const BOOST_NOEXCEPT {
+                        return rbegin();
+                    }
+
+                    const_reverse_iterator crend() const BOOST_NOEXCEPT {
+                        return rend();
+                    }
+
+                    size_type size() const BOOST_NOEXCEPT {
+                        return _hashes.size();
+                    }
+
+                    size_type complete_size() const BOOST_NOEXCEPT {
+                        return _size;
+                    }
+
+                    size_type capacity() const BOOST_NOEXCEPT {
+                        return _hashes.capacity();
+                    }
+
+                    bool empty() const BOOST_NOEXCEPT {
+                        return (_hashes.size() == 0);
+                    }
+
+                    size_type max_size() const BOOST_NOEXCEPT {
+                        return _hashes.max_size();
+                    }
+
+                    void reserve(size_type _n) {
+                        return _hashes.reserve(_n);
+                    }
+
+                    void shrink_to_fit() BOOST_NOEXCEPT {
+                        return _hashes.shrink_to_fit();
+                    }
+
+                    reference operator[](size_type _n) BOOST_NOEXCEPT {
+                        return _hashes[_n];
+                    }
+
+                    const_reference operator[](size_type _n) const BOOST_NOEXCEPT {
+                        return _hashes[_n];
+                    }
+
+                    reference at(size_type _n) {
+                        return _hashes.at(_n);
+                    }
+
+                    const_reference at(size_type _n) const {
+                        return _hashes.at(_n);
+                    }
+
+                    reference front() BOOST_NOEXCEPT {
+                        return _hashes.front();
+                    }
+
+                    const_reference front() const BOOST_NOEXCEPT {
+                        return _hashes.front();
+                    }
+
+                    reference back() BOOST_NOEXCEPT {
+                        return _hashes.back();
+                    }
+
+                    const_reference back() const BOOST_NOEXCEPT {
+                        return _hashes.back();
+                    }
+
+                    value_type *hashes() BOOST_NOEXCEPT {
+                        return _hashes;
+                    }
+
+                    const value_type *hashes() const BOOST_NOEXCEPT {
+                        return _hashes;
+                    }
+
+                    void push_back(const_reference _x) {
+                        //    #error ERROR
+                        _hashes.push_back(_x);
+                    }
+
+                    void push_back(value_type &&_x) {
+                        //    #error ERROR
+                        _hashes.push_back(_x);
+                    }
+
+                    //
+                    template<class... Args>
+                    reference emplace_back(Args &&..._args) {
+                        return _hashes.template emplace_back(_args...);
+                    }
+
+                    template<class... Args>
+                    iterator emplace(const_iterator _position, Args &&... _args) {
+                        return _hashes.template emplace(_position, _args...);
+                    }
+
+                    void pop_back() {
+                        _hashes.pop_back();
+                    }
+
+                    void clear() BOOST_NOEXCEPT {
+                        _hashes.clear();
+                    }
+
+                    void resize(size_type _sz) {
+                        return _hashes.resize(_sz);
+                    }
+
+                    void resize(size_type _sz, const_reference _x) {
+                        return _hashes.resize(_sz, _x);
+                    }
+
+                    void swap(merkle_tree_impl &other) {
+                        _hashes.swap(other.hashes());
+                        std::swap(_leaves, other.leaves());
+                        std::swap(_rc, other.rc());
+                        std::swap(_size, other.size());
+                    }
+
+                    value_type root() const BOOST_NOEXCEPT {
+                        BOOST_ASSERT_MSG(_size == _hashes.size(), "MerkleTree not fulfilled");
+                        return _hashes[_size - 1];
+                    }
+
+                    value_type root() BOOST_NOEXCEPT {
+                        BOOST_ASSERT_MSG(_size == _hashes.size(), "MerkleTree not fulfilled");
+                        return _hashes[_size - 1];
+                    }
+
+                    size_t row_count() const {
+                        return _rc;
+                    }
+
+                    size_t leaves() const {
+                        return _leaves;
+                    }
+
+                    void set_leaves(size_t s) {
+                        _leaves = s;
+                    }
+
+                    void set_row_count(size_t s) {
+                        _rc = s;
+                    }
+
+                    void set_complete_size(size_t s) {
+                        _size = s;
+                    }
+
+                protected:
+                    container_type _hashes;
+
+                    size_t _size;
+                    size_t _leaves;
+                    // Note: The former 'upstream' merkle_light project uses 'height'
+                    // (with regards to the tree property) incorrectly, so we've
+                    // renamed it since it's actually a '_rc'.  For example, a
+                    // tree with 2 leaf nodes and a single root node has a height of
+                    // 1, but a _rc of 2.
+                    //
+                    // Internally, this code considers only the _rc.
+                    size_t _rc;
+                };
+
+                template<typename T, typename LeafIterator>
+                typename T::digest_type generate_hash(LeafIterator first, LeafIterator last) {
+                    accumulator_set<T> acc;
+                    while (first != last) {
+                        crypto3::hash<T>(*first++, acc);
+                    }
+                    return accumulators::extract::hash<T>(acc);
+                }
+
+                template<typename T, std::size_t Arity, typename LeafIterator>
+                merkle_tree_impl<T, Arity> make_merkle_tree(LeafIterator first, LeafIterator last) {
+                    typedef T node_type;
+                    typedef typename node_type::hash_type hash_type;
+                    typedef typename node_type::value_type value_type;
+                    typedef typename std::iterator_traits<LeafIterator>::value_type leaf_value_type;
+
+                    merkle_tree_impl<T, Arity> ret(std::distance(first, last));
+                    ret.resize(ret.complete_size());
+
+                    nil::crypto3::parallel_transform(first, last, ret.begin(), [](const leaf_value_type& leaf) {
+                        return static_cast<value_type>(crypto3::hash<hash_type>(leaf));
+                    });
+
+                    std::size_t row_idx = ret.leaves(), row_size = row_idx / Arity;
+                    typename merkle_tree_impl<T, Arity>::iterator it = ret.begin();
+
+                    std::size_t next_row_start_index = std::distance(first, last);
+
+                    for (size_t row_number = 1; row_number < ret.row_count(); ++row_number, row_size /= Arity) {
+                        nil::crypto3::parallel_for(0, row_size, [&ret, it, next_row_start_index](std::size_t index) {
+                            ret[next_row_start_index + index] = generate_hash<hash_type>(
+                                it + index * Arity, it + (index + 1) * Arity);
+                        });
+                        next_row_start_index += row_size;
+                        it += row_size * Arity;
+                    }
+                    return ret;
+                }
+            }    // namespace detail
+
+            template<typename T, std::size_t Arity>
+            using merkle_tree = typename std::conditional<nil::crypto3::detail::is_hash<T>::value,
+                    detail::merkle_tree_impl<detail::merkle_tree_node<T>, Arity>,
+                    detail::merkle_tree_impl<T, Arity>>::type;
+
+            template<typename T, std::size_t Arity, typename LeafIterator>
+            merkle_tree<T, Arity> make_merkle_tree(LeafIterator first, LeafIterator last) {
+                return detail::make_merkle_tree<typename std::conditional<nil::crypto3::detail::is_hash<T>::value,
+                        detail::merkle_tree_node<T>,
+                        T>::type,
+                        Arity>(first, last);
+            }
+
+        }    // namespace containers
+    }        // namespace crypto3
+}    // namespace nil
+
+#endif    // CRYPTO3_MERKLE_TREE_HPP
diff --git a/libs/parallel-containers/include/nil/crypto3/container/sparse_vector.hpp b/libs/parallel-containers/include/nil/crypto3/container/sparse_vector.hpp
new file mode 100644
index 00000000..c074e3b2
--- /dev/null
+++ b/libs/parallel-containers/include/nil/crypto3/container/sparse_vector.hpp
@@ -0,0 +1,298 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
+//
+// 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.
+//---------------------------------------------------------------------------//
+// @file Declaration of interfaces for a sparse vector.
+//---------------------------------------------------------------------------//
+
+#ifndef CRYPTO3_ZK_SPARSE_VECTOR_HPP
+#define CRYPTO3_ZK_SPARSE_VECTOR_HPP
+
+#include <iostream>
+#include <vector>
+#include <numeric>
+
+#include <nil/crypto3/algebra/multiexp/multiexp.hpp>
+#include <nil/crypto3/algebra/multiexp/policies.hpp>
+
+namespace nil {
+    namespace crypto3 {
+        namespace container {
+
+            /**
+             * A sparse vector is a list of indices along with corresponding values.
+             * The indices are selected from the set {0,1,...,domain_size-1}.
+             */
+            template<typename Type>
+            class sparse_vector {
+                using underlying_value_type = typename Type::value_type;
+
+                template<typename T>
+                using container_type = std::vector<T>;
+
+                typedef container_type<underlying_value_type> value_container_type;
+
+            public:
+                using group_type = Type;
+
+                typedef typename value_container_type::value_type value_type;
+                typedef typename value_container_type::allocator_type allocator_type;
+                typedef typename value_container_type::reference reference;
+                typedef typename value_container_type::const_reference const_reference;
+                typedef typename value_container_type::size_type size_type;
+                typedef typename value_container_type::difference_type difference_type;
+                typedef typename value_container_type::pointer pointer;
+                typedef typename value_container_type::const_pointer const_pointer;
+                typedef typename value_container_type::iterator iterator;
+                typedef typename value_container_type::const_iterator const_iterator;
+                typedef typename value_container_type::reverse_iterator reverse_iterator;
+                typedef typename value_container_type::const_reverse_iterator const_reverse_iterator;
+
+                container_type<std::size_t> indices;
+                container_type<underlying_value_type> values;
+                std::size_t domain_size_;
+
+                sparse_vector() = default;
+
+                sparse_vector(const sparse_vector<Type> &other) = default;
+
+                sparse_vector(sparse_vector<Type> &&other) = default;
+
+                sparse_vector(value_container_type &&v) : values(std::move(v)), domain_size_(values.size()) {
+                    indices.resize(domain_size_);
+                    std::iota(indices.begin(), indices.end(), 0);
+                }
+
+                explicit sparse_vector(size_type n) : values(n) {
+                }
+                explicit sparse_vector(size_type n, const allocator_type &a) : values(n, a) {
+                }
+
+                sparse_vector(size_type n, const value_type &x) : values(n, x) {
+                }
+                sparse_vector(size_type n, const value_type &x, const allocator_type &a) : values(n, x, a) {
+                }
+                template<typename InputIterator>
+                sparse_vector(InputIterator first, InputIterator last) : values(first, last) {
+                }
+                template<typename InputIterator>
+                sparse_vector(InputIterator first, InputIterator last, const allocator_type &a) :
+                    values(first, last, a) {
+                }
+
+                ~sparse_vector() = default;
+
+                sparse_vector(std::initializer_list<value_type> il) : values(il) {
+                }
+
+                sparse_vector(std::initializer_list<value_type> il, const allocator_type &a) : values(il, a) {
+                }
+
+                sparse_vector<Type> &operator=(const sparse_vector<Type> &other) = default;
+                sparse_vector<Type> &operator=(sparse_vector<Type> &&other) = default;
+
+                underlying_value_type operator[](const std::size_t idx) const {
+                    auto it = std::lower_bound(indices.begin(), indices.end(), idx);
+                    return (it != indices.end() && *it == idx) ? values[it - indices.begin()] : underlying_value_type();
+                }
+
+                bool operator==(const sparse_vector<Type> &other) const {
+                    if (this->domain_size_ != other.domain_size_) {
+                        return false;
+                    }
+
+                    std::size_t this_pos = 0, other_pos = 0;
+                    while (this_pos < this->indices.size() && other_pos < other.indices.size()) {
+                        if (this->indices[this_pos] == other.indices[other_pos]) {
+                            if (this->values[this_pos] != other.values[other_pos]) {
+                                return false;
+                            }
+                            ++this_pos;
+                            ++other_pos;
+                        } else if (this->indices[this_pos] < other.indices[other_pos]) {
+                            if (!this->values[this_pos].is_zero()) {
+                                return false;
+                            }
+                            ++this_pos;
+                        } else {
+                            if (!other.values[other_pos].is_zero()) {
+                                return false;
+                            }
+                            ++other_pos;
+                        }
+                    }
+
+                    /* at least one of the vectors has been exhausted, so other must be empty */
+                    while (this_pos < this->indices.size()) {
+                        if (!this->values[this_pos].is_zero()) {
+                            return false;
+                        }
+                        ++this_pos;
+                    }
+
+                    while (other_pos < other.indices.size()) {
+                        if (!other.values[other_pos].is_zero()) {
+                            return false;
+                        }
+                        ++other_pos;
+                    }
+
+                    return true;
+                }
+
+                bool operator==(const value_container_type &other) const {
+                    if (this->domain_size_ < other.size()) {
+                        return false;
+                    }
+
+                    std::size_t j = 0;
+                    for (std::size_t i = 0; i < other.size(); ++i) {
+                        if (this->indices[j] == i) {
+                            if (this->values[j] != other[j]) {
+                                return false;
+                            }
+                            ++j;
+                        } else {
+                            if (!other[j].is_zero()) {
+                                return false;
+                            }
+                        }
+                    }
+
+                    return true;
+                }
+
+                bool is_valid() const {
+                    if (values.size() == indices.size() && values.size() <= domain_size_) {
+                        return false;
+                    }
+
+                    for (std::size_t i = 0; i + 1 < indices.size(); ++i) {
+                        if (indices[i] >= indices[i + 1]) {
+                            return false;
+                        }
+                    }
+
+                    if (!indices.empty() && indices[indices.size() - 1] >= domain_size_) {
+                        return false;
+                    }
+
+                    return true;
+                }
+
+                bool empty() const {
+                    return indices.empty();
+                }
+
+                std::size_t domain_size() const {
+                    return domain_size_;
+                }
+
+                std::size_t size() const {
+                    return indices.size();
+                }
+
+                std::size_t size_in_bits() const {
+                    return indices.size() * (sizeof(std::size_t) * 8 + Type::value_bits);
+                }
+
+                /* return a pair consisting of the accumulated value and the sparse vector of non-accumulated values
+                 */
+                template<typename InputBaseIterator>
+                std::pair<underlying_value_type, sparse_vector<Type>>
+                    insert(std::size_t offset, InputBaseIterator first, InputBaseIterator last) const {
+#ifdef MULTICORE
+                    const std::size_t chunks = omp_get_max_threads();    // to override, set OMP_NUM_THREADS env var
+                                                                         // or call omp_set_num_threads()
+#else
+                    const std::size_t chunks = 1;
+#endif
+
+                    underlying_value_type accumulated_value = underlying_value_type::zero();
+                    sparse_vector<Type> resulting_vector;
+                    resulting_vector.domain_size_ = domain_size_;
+
+                    const std::size_t range_len = std::distance(first, last);
+                    bool in_block = false;
+                    std::size_t first_pos = -1,
+                                last_pos = -1;    // g++ -flto emits unitialized warning, even though in_block
+                    // guards for such cases.
+
+                    for (std::size_t i = 0; i < indices.size(); ++i) {
+                        const bool matching_pos = (offset <= indices[i] && indices[i] < offset + range_len);
+                        // printf("i = %zu, pos[i] = %zu, offset = %zu, w_size = %zu\n", i, indices[i], offset,
+                        // w_size);
+                        bool copy_over;
+
+                        if (in_block) {
+                            if (matching_pos && last_pos == i - 1) {
+                                // block can be extended, do it
+                                last_pos = i;
+                                copy_over = false;
+                            } else {
+                                // block has ended here
+                                in_block = false;
+                                copy_over = true;
+
+                                accumulated_value = accumulated_value +
+                                                    algebra::multiexp<algebra::policies::multiexp_method_bos_coster>(
+                                                        values.begin() + first_pos, values.begin() + last_pos + 1,
+                                                        first + (indices[first_pos] - offset),
+                                                        last + (indices[last_pos] - offset) + 1, chunks);
+                            }
+                        } else {
+                            if (matching_pos) {
+                                // block can be started
+                                first_pos = i;
+                                last_pos = i;
+                                in_block = true;
+                                copy_over = false;
+                            } else {
+                                copy_over = true;
+                            }
+                        }
+
+                        if (copy_over) {
+                            resulting_vector.indices.emplace_back(indices[i]);
+                            resulting_vector.values.emplace_back(values[i]);
+                        }
+                    }
+
+                    if (in_block) {
+                        accumulated_value =
+                            accumulated_value + algebra::multiexp<algebra::policies::multiexp_method_bos_coster>(
+                                                    values.begin() + first_pos,
+                                                    values.begin() + last_pos + 1,
+                                                    first + (indices[first_pos] - offset),
+                                                    first + (indices[last_pos] - offset) + 1,
+                                                    chunks);
+                    }
+
+                    return std::make_pair(accumulated_value, resulting_vector);
+                }
+            };
+        }    // namespace container
+    }        // namespace crypto3
+}    // namespace nil
+
+#endif    // CRYPTO3_ZK_SPARSE_VECTOR_HPP
diff --git a/libs/parallel-containers/test/CMakeLists.txt b/libs/parallel-containers/test/CMakeLists.txt
new file mode 100644
index 00000000..6451bb72
--- /dev/null
+++ b/libs/parallel-containers/test/CMakeLists.txt
@@ -0,0 +1,86 @@
+#---------------------------------------------------------------------------//
+#  MIT License
+#
+#  Copyright (c) 2020 Mikhail Komarov <nemo@nil.foundation>
+#  Copyright (c) 2021 Aleksei Moskvin <alalmoskvin@gmail.com>
+#
+#  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(CMTest)
+
+cm_test_link_libraries(${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME}
+
+                       crypto3::algebra
+                       crypto3::hash
+
+                       Boost::random)
+
+macro(define_storage_test test)
+    get_filename_component(test_name ${test} NAME)
+    set(target_name ${CMAKE_WORKSPACE_NAME}_${CURRENT_PROJECT_NAME}_${test_name}_test)
+
+    while(TARGET ${target_name})
+        get_filename_component(TEST_DIRECTORY ${test} DIRECTORY)
+        get_filename_component(PARENT_DIR ${TEST_DIRECTORY} DIRECTORY)
+        set(target_name ${PARENT_DIR}_${target_name})
+    endwhile()
+
+    set(additional_args "")
+    if(ENABLE_JUNIT_TEST_OUTPUT)
+        set(TEST_RESULTS_DIR "${CMAKE_CURRENT_BINARY_DIR}/junit_results")
+        set(TEST_LOGS_DIR "${TEST_RESULTS_DIR}/logs")
+        file(MAKE_DIRECTORY ${TEST_LOGS_DIR})
+
+        set(additional_args "--log_format=JUNIT"
+                            "--log_sink=${TEST_LOGS_DIR}/${target_name}.xml")
+    endif()
+
+    cm_test(NAME ${target_name} SOURCES ${test}.cpp ARGS ${additional_args})
+
+    target_include_directories(${target_name} PRIVATE
+                               "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
+                               "$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>"
+
+                               ${Boost_INCLUDE_DIRS})
+
+    set_target_properties(${target_name} PROPERTIES CXX_STANDARD 17)
+
+    get_target_property(target_type Boost::unit_test_framework TYPE)
+    if(target_type STREQUAL "SHARED_LIB")
+        target_compile_definitions(${target_name} PRIVATE BOOST_TEST_DYN_LINK)
+    elseif(target_type STREQUAL "STATIC_LIB")
+
+    endif()
+
+     if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+        target_compile_options(${target_name} PRIVATE "-fconstexpr-steps=2147483647" "-ftemplate-backtrace-limit=0")
+    elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+        target_compile_options(${target_name} PRIVATE "-fconstexpr-ops-limit=4294967295" "-ftemplate-backtrace-limit=0")
+    endif()
+
+endmacro()
+
+set(TESTS_NAMES
+    "merkle/merkle"
+)
+
+foreach(TEST_NAME ${TESTS_NAMES})
+    define_storage_test(${TEST_NAME})
+endforeach()
diff --git a/libs/parallel-containers/test/merkle/merkle.cpp b/libs/parallel-containers/test/merkle/merkle.cpp
new file mode 100644
index 00000000..f3d8dc2d
--- /dev/null
+++ b/libs/parallel-containers/test/merkle/merkle.cpp
@@ -0,0 +1,400 @@
+//---------------------------------------------------------------------------//
+// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
+// Copyright (c) 2021-2022 Aleksei Moskvin <alalmoskvin@gmail.com>
+// Copyright (c) 2021 Ilias Khairullin <ilias@nil.foundation>
+//
+// 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 containter_merkletree_test
+
+#include <nil/crypto3/algebra/random_element.hpp>
+#include <nil/crypto3/algebra/type_traits.hpp>
+#include <nil/crypto3/hash/block_to_field_elements_wrapper.hpp>
+#include <nil/crypto3/hash/sha2.hpp>
+#include <nil/crypto3/hash/md5.hpp>
+#include <nil/crypto3/hash/blake2b.hpp>
+#include <nil/crypto3/hash/keccak.hpp>
+#include <nil/crypto3/hash/pedersen.hpp>
+#include <nil/crypto3/hash/poseidon.hpp>
+
+#include <nil/crypto3/container/merkle/tree.hpp>
+#include <nil/crypto3/container/merkle/proof.hpp>
+
+#include <boost/test/unit_test.hpp>
+#include <boost/test/data/test_case.hpp>
+#include <boost/test/data/monomorphic.hpp>
+
+#include <chrono>
+#include <cstdio>
+#include <limits>
+#include <type_traits>
+#include <nil/crypto3/hash/algorithm/hash.hpp>
+
+using namespace nil::crypto3;
+using namespace nil::crypto3::containers;
+
+template<typename ValueType, std::size_t N>
+typename std::enable_if<std::is_unsigned<ValueType>::value, std::vector<std::array<ValueType, N>>>::type
+    generate_random_data(std::size_t leaf_number) {
+    std::vector<std::array<ValueType, N>> v;
+    for (std::size_t i = 0; i < leaf_number; ++i) {
+        std::array<ValueType, N> leaf {};
+        std::generate(std::begin(leaf), std::end(leaf),
+                      [&]() { return std::rand() % (std::numeric_limits<ValueType>::max() + 1); });
+        v.emplace_back(leaf);
+    }
+    return v;
+}
+
+template<typename ValueType, std::size_t N>
+typename std::enable_if<algebra::is_field_element<ValueType>::value, std::vector<std::array<ValueType, N>>>::type
+    generate_random_data(std::size_t leaf_number) {
+    std::vector<std::array<ValueType, N>> v;
+    for (std::size_t i = 0; i < leaf_number; ++i) {
+        std::array<ValueType, N> leaf {};
+        std::generate(std::begin(leaf), std::end(leaf),
+                      [&]() { return algebra::random_element<typename ValueType::field_type>(); });
+        v.emplace_back(leaf);
+    }
+    return v;
+}
+
+template<typename Hash, size_t Arity, typename ValueType, std::size_t N>
+void testing_validate_template_random_data(std::size_t leaf_number) {
+    std::array<ValueType, N> data_not_in_tree = {0u};
+    auto data = generate_random_data<ValueType, N>(leaf_number);
+    auto tree = make_merkle_tree<Hash, Arity>(data.begin(), data.end());
+
+    std::size_t proof_idx = std::rand() % leaf_number;
+    merkle_proof<Hash, Arity> proof(tree, proof_idx);
+    bool good_validate = proof.validate(data[proof_idx]);
+    bool wrong_leaf_validate = proof.validate(data[(proof_idx + 1) % leaf_number]);
+    bool wrong_data_validate = proof.validate(data_not_in_tree);
+    BOOST_CHECK(good_validate);
+    BOOST_CHECK(!wrong_leaf_validate);
+    BOOST_CHECK(!wrong_data_validate);
+}
+
+template<typename Hash, size_t Arity, typename Element>
+void testing_validate_template(std::vector<Element> data) {
+    std::array<uint8_t, 7> data_not_in_tree = {'\x6d', '\x65', '\x73', '\x73', '\x61', '\x67', '\x65'};
+    merkle_tree<Hash, Arity> tree = make_merkle_tree<Hash, Arity>(data.begin(), data.end());
+    merkle_tree<Hash, Arity> tree2(tree.begin(), tree.end());
+//    for (auto i = 0; i < tree.size(); ++i) {
+//        std::cout << tree[i] << std::endl;
+//    }
+//    tree.emplace_back(nil::crypto3::hash<typename Hash::hash_type>(data_not_in_tree[0]));
+    merkle_proof<Hash, Arity> proof(tree, 0);
+    bool good_validate = proof.validate(data[0]);
+    bool wrong_leaf_validate = proof.validate(data[1]);
+    bool wrong_data_validate = proof.validate(data_not_in_tree);
+    BOOST_CHECK(true == good_validate);
+    BOOST_CHECK(false == wrong_leaf_validate);
+    BOOST_CHECK(false == wrong_data_validate);
+}
+
+template<typename Hash, size_t Arity, typename ValueType, std::size_t N>
+void testing_validate_template_random_data_compressed_proofs(std::size_t leaf_number) {
+    using merkle_proof_type = typename containers::merkle_proof<Hash, Arity>;
+    using Element = std::array<ValueType, N>;
+    std::array<ValueType, N> data_not_in_tree = {0};
+    auto data = generate_random_data<ValueType, N>(leaf_number);
+    auto tree = make_merkle_tree<Hash, Arity>(data.begin(), data.end());
+
+    std::size_t num_idxs = std::rand() % leaf_number;
+    while (num_idxs == 0) {
+        num_idxs = std::rand() % leaf_number;
+    }
+
+    std::vector<std::size_t> proof_idxs;
+    std::vector<Element> data_for_validation;
+    for (std::size_t i = 0; i < num_idxs; ++i) {
+        proof_idxs.emplace_back(std::rand() % leaf_number);
+    }
+    for (auto idx : proof_idxs) {
+        data_for_validation.emplace_back(data[idx]);
+    }
+
+    // standard case
+    auto start = std::chrono::high_resolution_clock::now();
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs = merkle_proof_type::generate_compressed_proofs(tree, proof_idxs);
+    bool validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_for_validation);
+    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start);
+    // case for arity == 4
+    if (leaf_number == 16) {
+        std::vector<merkle_proof<Hash, Arity>> compressed_proofs_one_idx = merkle_proof_type::generate_compressed_proofs(tree, {3, 2, 1, 5, 11, 11, 0});
+        bool validate_compressed_one_idx = merkle_proof_type::validate_compressed_proofs(compressed_proofs_one_idx, std::vector<Element>({data[3], data[2], data[1], data[5], data[11], data[11], data[0]}));
+        BOOST_CHECK(validate_compressed_one_idx);
+    }
+    // one index
+    std::size_t one_idx = std::rand() % leaf_number;
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_one_idx = merkle_proof_type::generate_compressed_proofs(tree, {one_idx});
+    bool validate_compressed_one_idx = merkle_proof_type::validate_compressed_proofs(compressed_proofs_one_idx, std::vector<Element>({data[one_idx]}));
+    // edge indexes
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_edge_idxs = merkle_proof_type::generate_compressed_proofs(tree, {0, leaf_number - 1});
+    bool validate_compressed_edge_idxs = merkle_proof_type::validate_compressed_proofs(compressed_proofs_edge_idxs, std::vector<Element>({data[0], data[leaf_number - 1]}));
+    // repeated indexes
+    std::size_t repeated_idx = std::rand() % leaf_number;
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_repeated_idxs = merkle_proof_type::generate_compressed_proofs(tree, {repeated_idx, leaf_number / 2, repeated_idx});
+    bool validate_compressed_repeated_idxs = merkle_proof_type::validate_compressed_proofs(compressed_proofs_repeated_idxs, std::vector<Element>({data[repeated_idx], data[leaf_number / 2], data[repeated_idx]}));
+    // wrong leaf
+    auto sorted_idxs = proof_idxs;
+    std::sort(sorted_idxs.begin(), sorted_idxs.end());
+    std::size_t wrong_leaf_idx = 0;
+    for (auto idx : sorted_idxs) {
+        if (idx == wrong_leaf_idx) {
+            wrong_leaf_idx++;
+        } else {
+            break;
+        }
+    }
+    auto data_wrong_leaf = data_for_validation;
+    data_wrong_leaf[std::rand() % num_idxs] = data[wrong_leaf_idx];
+    assert(data_wrong_leaf != data_for_validation);
+    bool wrong_leaf_validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_wrong_leaf);
+    // wrong data
+    auto data_wrong_data = data_for_validation;
+    data_wrong_data[std::rand() % num_idxs] = data_not_in_tree;
+    assert(data_wrong_data != data_for_validation);
+    bool wrong_data_validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_wrong_data);
+
+    BOOST_CHECK(validate_compressed);
+    BOOST_CHECK(validate_compressed_one_idx);
+    BOOST_CHECK(validate_compressed_edge_idxs);
+    BOOST_CHECK(validate_compressed_repeated_idxs);
+    BOOST_CHECK(!wrong_leaf_validate_compressed);
+    BOOST_CHECK(!wrong_data_validate_compressed);
+}
+
+template<typename Hash, size_t Arity, typename Element>
+void testing_validate_template_compressed_proofs(std::vector<Element> data) {
+    using merkle_proof_type = typename containers::merkle_proof<Hash, Arity>;
+    merkle_tree<Hash, Arity> tree = make_merkle_tree<Hash, Arity>(data.begin(), data.end());
+
+    std::size_t leaf_number = data.size();
+    std::size_t num_idxs = std::rand() % leaf_number;
+    while (num_idxs == 0) {
+        num_idxs = std::rand() % leaf_number;
+    }
+    std::vector<std::size_t> proof_idxs;
+    std::vector<Element> data_for_validation;
+    for (std::size_t i = 0; i < num_idxs; ++i) {
+        proof_idxs.emplace_back(std::rand() % leaf_number);
+    }
+    for (auto idx : proof_idxs) {
+        data_for_validation.emplace_back(data[idx]);
+    }
+
+    // standart case
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs = merkle_proof_type::generate_compressed_proofs(tree, proof_idxs);
+    bool validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_for_validation);
+    // one index
+    std::size_t one_idx = std::rand() % leaf_number;
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_one_idx = merkle_proof_type::generate_compressed_proofs(tree, {one_idx});
+    bool validate_compressed_one_idx = merkle_proof_type::validate_compressed_proofs(compressed_proofs_one_idx, std::vector<Element>({data[one_idx]}));
+    // edge indexes
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_edge_idxs = merkle_proof_type::generate_compressed_proofs(tree, {0, leaf_number - 1});
+    bool validate_compressed_edge_idxs = merkle_proof_type::validate_compressed_proofs(compressed_proofs_edge_idxs, std::vector<Element>({data[0], data[leaf_number - 1]}));
+    // repeated indexes
+    std::size_t repeated_idx = std::rand() % leaf_number;
+    std::vector<merkle_proof<Hash, Arity>> compressed_proofs_repeated_idxs = merkle_proof_type::generate_compressed_proofs(tree, {repeated_idx, leaf_number - 1, repeated_idx});
+    bool validate_compressed_repeated_idxs = merkle_proof_type::validate_compressed_proofs(compressed_proofs_repeated_idxs, std::vector<Element>({data[repeated_idx], data[leaf_number - 1], data[repeated_idx]}));
+    // wrong leaf
+    auto sorted_idxs = proof_idxs;
+    std::sort(sorted_idxs.begin(), sorted_idxs.end());
+    std::size_t wrong_leaf_idx = 0;
+    for (auto idx : sorted_idxs) {
+        if (idx == wrong_leaf_idx) {
+            wrong_leaf_idx++;
+        } else {
+            break;
+        }
+    }
+    auto data_wrong_leaf = data_for_validation;
+    data_wrong_leaf[std::rand() % num_idxs] = data[wrong_leaf_idx];
+    assert(data_wrong_leaf != data_for_validation);
+    bool wrong_leaf_validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_wrong_leaf);
+    // wrong data
+    auto data_wrong_data = data_for_validation;
+    data_wrong_data[std::rand() % num_idxs] = {'9'};
+    assert(data_wrong_data != data_for_validation);
+    bool wrong_data_validate_compressed = merkle_proof_type::validate_compressed_proofs(compressed_proofs, data_wrong_data);
+
+    BOOST_CHECK(validate_compressed);
+    BOOST_CHECK(validate_compressed_one_idx);
+    BOOST_CHECK(validate_compressed_edge_idxs);
+    BOOST_CHECK(validate_compressed_repeated_idxs);
+    BOOST_CHECK(!wrong_leaf_validate_compressed);
+    BOOST_CHECK(!wrong_data_validate_compressed);
+}
+
+template<typename Hash, size_t Arity, typename Element>
+void testing_hash_template(std::vector<Element> data, std::string result) {
+    merkle_tree<Hash, Arity> tree = make_merkle_tree<Hash, Arity>(data.begin(), data.end());
+    BOOST_CHECK(result == std::to_string(tree.root()));
+}
+
+BOOST_AUTO_TEST_SUITE(containers_merkltree_test)
+
+using curve_type = algebra::curves::pallas;
+using field_type = typename curve_type::base_field_type;
+using poseidon_type = hashes::poseidon<nil::crypto3::hashes::detail::mina_poseidon_policy<field_type>>;
+using original_poseidon_type = hashes::original_poseidon<nil::crypto3::hashes::detail::mina_poseidon_policy<field_type>>;
+
+BOOST_AUTO_TEST_CASE(merkletree_construct_test_1) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}};
+    merkle_tree<hashes::sha2<256>, 2> tree_res = make_merkle_tree<hashes::sha2<256>, 2>(v.begin(), v.end());
+    merkle_tree<hashes::sha2<256>, 2> tree(tree_res.begin(), tree_res.end());
+    BOOST_CHECK_EQUAL(tree.size(), 15);
+    BOOST_CHECK_EQUAL(tree.leaves(), 8);
+    BOOST_CHECK_EQUAL(tree.row_count(), 4);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_construct_test_2) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}};
+    merkle_tree<hashes::sha2<256>, 3> tree_res = make_merkle_tree<hashes::sha2<256>, 3>(v.begin(), v.end());
+    merkle_tree<hashes::sha2<256>, 3> tree(tree_res.begin(), tree_res.end());
+    BOOST_CHECK_EQUAL(tree.size(), 13);
+    BOOST_CHECK_EQUAL(tree.leaves(), 9);
+    BOOST_CHECK_EQUAL(tree.row_count(), 3);
+}
+
+
+BOOST_AUTO_TEST_CASE(merkletree_validate_test_1) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}};
+    testing_validate_template<hashes::sha2<256>, 2>(v);
+    testing_validate_template<hashes::md5, 2>(v);
+    testing_validate_template<hashes::blake2b<224>, 2>(v);
+
+    BOOST_STATIC_ASSERT_MSG(algebra::is_field_element<original_poseidon_type::word_type>::value, "Expecting Poseidon to consume field elements");
+    std::vector<std::array<original_poseidon_type::word_type, 1>> v_field = {
+        {0x0_cppui_modular255},
+        {0x1_cppui_modular255},
+        {0x2_cppui_modular255},
+        {0x3_cppui_modular255},
+        {0x4_cppui_modular255},
+        {0x5_cppui_modular255},
+        {0x6_cppui_modular255},
+        {0x7_cppui_modular255}
+    };
+    testing_validate_template<original_poseidon_type, 2>(v_field);
+    testing_validate_template<poseidon_type, 2>(v_field);
+    // When you have bytes input, use wrapper to lazy convert it to field elements:
+    std::vector<
+        nil::crypto3::hashes::block_to_field_elements_wrapper<
+            typename poseidon_type::word_type::field_type,
+            std::array<char, 1>
+        >
+    > wrappers;
+    for (const auto& inner_containers : v) {
+        wrappers.emplace_back(inner_containers);
+    }
+    testing_validate_template<original_poseidon_type, 2>(wrappers);
+    testing_validate_template<poseidon_type, 2>(wrappers);
+
+    std::size_t leaf_number = 8;
+    testing_validate_template_random_data<hashes::sha2<256>, 2, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data<hashes::md5, 2, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data<hashes::blake2b<224>, 2, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data<original_poseidon_type, 2, original_poseidon_type::word_type, 1>(leaf_number);
+    testing_validate_template_random_data<poseidon_type, 2, poseidon_type::word_type, 1>(leaf_number);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_validate_test_2) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}};
+    testing_validate_template<hashes::sha2<256>, 3>(v);
+    testing_validate_template<hashes::md5, 3>(v);
+    testing_validate_template<hashes::blake2b<224>, 3>(v);
+
+    std::size_t leaf_number = 9;
+    testing_validate_template_random_data<hashes::sha2<256>, 3, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data<hashes::md5, 3, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data<hashes::blake2b<224>, 3, std::uint8_t, 1>(leaf_number);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_validate_test_3) {
+    using hash_type = hashes::pedersen<
+        hashes::find_group_hash_default_params, hashes::sha2<256>,
+        algebra::curves::jubjub::template g1_type<nil::crypto3::algebra::curves::coordinates::affine,
+                                                  nil::crypto3::algebra::curves::forms::twisted_edwards>>;
+    std::size_t leaf_number = 8;
+    testing_validate_template_random_data<hash_type, 2, bool, hash_type::digest_bits>(leaf_number);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_validate_test_4) {
+    using hash_type = hashes::pedersen<
+        hashes::find_group_hash_default_params, hashes::sha2<256>,
+        algebra::curves::jubjub::template g1_type<nil::crypto3::algebra::curves::coordinates::affine,
+                                                  nil::crypto3::algebra::curves::forms::twisted_edwards>>;
+    testing_validate_template_random_data_compressed_proofs<hash_type, 2, bool, hash_type::digest_bits>(8);
+    testing_validate_template_random_data_compressed_proofs<hash_type, 3, bool, hash_type::digest_bits>(9);
+    testing_validate_template_random_data_compressed_proofs<hash_type, 4, bool, hash_type::digest_bits>(16);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_validate_test_5) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}};
+    testing_validate_template_compressed_proofs<hashes::sha2<256>, 3>(v);
+    testing_validate_template_compressed_proofs<hashes::md5, 3>(v);
+    testing_validate_template_compressed_proofs<hashes::blake2b<224>, 3>(v);
+
+    std::size_t leaf_number = 16;
+    testing_validate_template_random_data_compressed_proofs<hashes::sha2<256>, 4, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data_compressed_proofs<hashes::md5, 4, std::uint8_t, 1>(leaf_number);
+    testing_validate_template_random_data_compressed_proofs<hashes::blake2b<224>, 4, std::uint8_t, 1>(leaf_number);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_hash_test_1) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}};
+    testing_hash_template<hashes::sha2<256>, 2>(v, "3b828c4f4b48c5d4cb5562a474ec9e2fd8d5546fae40e90732ef635892e42720");
+    testing_hash_template<hashes::md5, 2>(v, "11ee8b50825ce6f816a1ae06d4aa0045");
+    testing_hash_template<hashes::blake2b<224>, 2>(v, "0ed2a2145cae554ca57f08420d6cb58629ca1e89dc92f819c6c1d13d");
+    testing_hash_template<hashes::keccak_1600<256>, 2>(v, "568ff5eb286f51b8a3e8de4e53aa8daed44594a246deebbde119ea2eb27acd6b");
+    testing_hash_template<hashes::keccak_1600<512>, 2>(v, "1a0ca31dd9e0b27afdf77021dc50023cdd814eb53ede16e8c5c322a0bcb6bd7d26a0404e5af53971e1566c1649bb9686905cdedfa9a358023065e423522d4372");
+
+    std::vector<std::string> v_64 = {
+        "0123456789012345678901234567890123456789012345678901234567890123",
+        "0123456789012345678901234567890123456789012345678901234567890123",
+        "0123456789012345678901234567890123456789012345678901234567890123",
+        "0123456789012345678901234567890123456789012345678901234567890123"
+    };
+    std::vector<
+        nil::crypto3::hashes::block_to_field_elements_wrapper<
+            typename poseidon_type::word_type::field_type,
+            std::string,
+            /*OverflowOnPurpose=*/ true
+        >
+    > wrappers;
+    for (const auto& inner_containers : v_64) {
+        wrappers.emplace_back(inner_containers);
+    }
+    merkle_tree<poseidon_type, 2> tree = make_merkle_tree<poseidon_type, 2>(wrappers.begin(), wrappers.end());
+    BOOST_CHECK(tree.root() == 0x6E7641F1EAE17C0DA8227840EFEA6E1D17FB5EBA600D9DC34F314D5400E5BF3_cppui_modular255);
+}
+
+BOOST_AUTO_TEST_CASE(merkletree_hash_test_2) {
+    std::vector<std::array<char, 1>> v = {{'0'}, {'1'}, {'2'}, {'3'}, {'4'}, {'5'}, {'6'}, {'7'}, {'8'}};
+    testing_hash_template<hashes::sha2<256>, 3>(v, "6831d4d32538bedaa7a51970ac10474d5884701c840781f0a434e5b6868d4b73");
+    testing_hash_template<hashes::md5, 3>(v, "0733c4cd580b1523cfbb9751f42e9420");
+    testing_hash_template<hashes::blake2b<224>, 3>(v, "d9d0ff26d10aaac2882c08eb2b55e78690c949d1a73b1cfc0eb322ee");
+}
+
+BOOST_AUTO_TEST_SUITE_END()