diff --git a/.gitignore b/.gitignore index 19e3d48..6219317 100644 --- a/.gitignore +++ b/.gitignore @@ -353,3 +353,5 @@ MigrationBackup/ doc/html doc/xml build/* +_codeql_build_dir/ +_codeql_detected_source_root diff --git a/merklecpp.h b/merklecpp.h index 267e4d6..26ae030 100644 --- a/merklecpp.h +++ b/merklecpp.h @@ -1911,6 +1911,68 @@ namespace merkle throw std::runtime_error("EVP_Digest failed: " + std::to_string(rc)); } } + + /// @brief OpenSSL SHA384 + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + static inline void sha384_openssl( + const merkle::HashT<48>& l, + const merkle::HashT<48>& r, + merkle::HashT<48>& out) + { + uint8_t block[48 * 2]; + memcpy(&block[0], l.bytes, 48); + memcpy(&block[48], r.bytes, 48); + + const EVP_MD* md = EVP_sha384(); + int rc = + EVP_Digest(&block[0], sizeof(block), out.bytes, nullptr, md, nullptr); + if (rc != 1) + { + throw std::runtime_error("EVP_Digest failed: " + std::to_string(rc)); + } + } + + /// @brief OpenSSL SHA512 + /// @param l Left node hash + /// @param r Right node hash + /// @param out Output node hash + static inline void sha512_openssl( + const merkle::HashT<64>& l, + const merkle::HashT<64>& r, + merkle::HashT<64>& out) + { + uint8_t block[64 * 2]; + memcpy(&block[0], l.bytes, 64); + memcpy(&block[64], r.bytes, 64); + + const EVP_MD* md = EVP_sha512(); + int rc = + EVP_Digest(&block[0], sizeof(block), out.bytes, nullptr, md, nullptr); + if (rc != 1) + { + throw std::runtime_error("EVP_Digest failed: " + std::to_string(rc)); + } + } + + /// @brief Type of hashes in the SHA384 tree type + typedef HashT<48> Hash384; + + /// @brief Type of paths in the SHA384 tree type + typedef PathT<48, sha384_openssl> Path384; + + /// @brief SHA384 tree with OpenSSL hash function + typedef TreeT<48, sha384_openssl> Tree384; + + /// @brief Type of hashes in the SHA512 tree type + typedef HashT<64> Hash512; + + /// @brief Type of paths in the SHA512 tree type + typedef PathT<64, sha512_openssl> Path512; + + /// @brief SHA512 tree with OpenSSL hash function + typedef TreeT<64, sha512_openssl> Tree512; #endif /// @brief Type of hashes in the default tree type diff --git a/test/compare_hash_functions.cpp b/test/compare_hash_functions.cpp index 8d275bd..962c5c4 100644 --- a/test/compare_hash_functions.cpp +++ b/test/compare_hash_functions.cpp @@ -214,6 +214,32 @@ void bench( << std::endl; } +template +void benchT( + const std::vector>& hashes, + const std::string& name, + size_t root_interval) +{ + size_t j = 0; + auto start = std::chrono::high_resolution_clock::now(); + T mt; + for (auto& h : hashes) + { + mt.insert(h); + if ((j++ % root_interval) == 0) + mt.root(); + } + mt.root(); + auto stop = std::chrono::high_resolution_clock::now(); + double seconds = + std::chrono::duration_cast(stop - start).count() / + 1e9; + std::cout << std::left << std::setw(10) << name << ": " + << mt.statistics.num_insert << " insertions, " + << mt.statistics.num_root << " roots in " << seconds << " sec" + << std::endl; +} + #ifdef HAVE_EVERCRYPT template void bench_evercrypt( @@ -287,6 +313,20 @@ int main() bench(hashes, "OpenSSL", root_interval); #endif +#ifdef HAVE_OPENSSL + { + std::cout << "--- merklecpp trees with full SHA384: " << std::endl; + auto hashes384 = make_hashesT<48>(num_leaves); + benchT(hashes384, "OpenSSL", root_interval); + } + + { + std::cout << "--- merklecpp trees with full SHA512: " << std::endl; + auto hashes512 = make_hashesT<64>(num_leaves); + benchT(hashes512, "OpenSSL", root_interval); + } +#endif + #ifdef HAVE_EVERCRYPT bench(hashes, "EverCrypt", root_interval); #endif diff --git a/test/time_large_trees.cpp b/test/time_large_trees.cpp index 2546560..ed83083 100644 --- a/test/time_large_trees.cpp +++ b/test/time_large_trees.cpp @@ -46,6 +46,52 @@ int main() std::cout << "NEW: " << mt.statistics.to_string() << " in " << seconds << " sec" << std::endl; +#ifdef HAVE_OPENSSL + { + auto hashes384 = make_hashesT<48>(num_leaves); + + merkle::Tree384 mt384; + size_t j384 = 0; + auto start384 = std::chrono::high_resolution_clock::now(); + for (auto& h : hashes384) + { + mt384.insert(h); + if ((j384++ % root_interval) == 0) + mt384.root(); + } + mt384.root(); + auto stop384 = std::chrono::high_resolution_clock::now(); + double seconds384 = + std::chrono::duration_cast(stop384 - start384) + .count() / + 1e9; + std::cout << "SHA384: " << mt384.statistics.to_string() << " in " + << seconds384 << " sec" << std::endl; + } + + { + auto hashes512 = make_hashesT<64>(num_leaves); + + merkle::Tree512 mt512; + size_t j512 = 0; + auto start512 = std::chrono::high_resolution_clock::now(); + for (auto& h : hashes512) + { + mt512.insert(h); + if ((j512++ % root_interval) == 0) + mt512.root(); + } + mt512.root(); + auto stop512 = std::chrono::high_resolution_clock::now(); + double seconds512 = + std::chrono::duration_cast(stop512 - start512) + .count() / + 1e9; + std::cout << "SHA512: " << mt512.statistics.to_string() << " in " + << seconds512 << " sec" << std::endl; + } +#endif + #ifdef HAVE_EVERCRYPT std::vector ec_hashes; for (auto& h : hashes) diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index 318ff32..d5c70ae 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -111,4 +111,272 @@ TEST_CASE("Three-node tree") REQUIRE(copy.size() == 1); REQUIRE(copy.root() == h0); -} \ No newline at end of file +} + +#ifdef HAVE_OPENSSL +TEST_CASE("SHA384 empty tree") +{ + merkle::Tree384 tree; + + REQUIRE(tree.empty()); + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 0); + + REQUIRE_THROWS(tree.root()); + REQUIRE_THROWS(tree.path(0)); + REQUIRE_THROWS(tree.past_root(0)); + REQUIRE_THROWS(tree.past_path(0, 1)); + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.retract_to(0)); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + REQUIRE_NOTHROW(merkle::Tree384 dt(buffer)); +} + +TEST_CASE("SHA384 one-node tree") +{ + merkle::Tree384::Hash h; + merkle::Tree384 tree(h); + + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 0); + + REQUIRE(tree.root() == h); + REQUIRE(tree.leaf(0) == h); + REQUIRE(*tree.path(0)->root() == h); + REQUIRE(*tree.past_root(0) == h); + REQUIRE_THROWS(tree.past_root(1)); + REQUIRE(*tree.past_path(0, 0)->root() == h); + REQUIRE_THROWS(tree.past_path(0, 1)); + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.retract_to(0)); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + merkle::Tree384 dt(buffer); + REQUIRE(dt.root() == tree.root()); +} + +TEST_CASE("SHA384 three-node tree") +{ + merkle::Tree384::Hash h0, h1, hr; + h1.bytes[47] = 1; + + merkle::Tree384 tree; + + REQUIRE_NOTHROW(tree.insert(h0)); + REQUIRE_NOTHROW(tree.insert(h1)); + + hr = tree.root(); + + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 1); + + REQUIRE(tree.leaf(0) == h0); + REQUIRE(tree.leaf(1) == h1); + REQUIRE(*tree.path(0)->root() == hr); + REQUIRE(*tree.past_root(0) == h0); + REQUIRE(*tree.past_root(1) == hr); + + auto pp00 = tree.past_path(0, 0); + REQUIRE(pp00->size() == 0); + REQUIRE(pp00->leaf() == h0); + REQUIRE(*pp00->root() == h0); + + auto pp01 = tree.past_path(0, 1); + REQUIRE(pp01->size() == 1); + REQUIRE(pp01->leaf() == h0); + REQUIRE((*pp01)[0] == h1); + REQUIRE(*pp01->root() == hr); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + merkle::Tree384 dt(buffer); + REQUIRE(dt.root() == tree.root()); + + merkle::Tree384 copy = tree; + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.flush_to(1)); + REQUIRE_THROWS(tree.retract_to(0)); + + REQUIRE_NOTHROW(copy.flush_to(0)); + REQUIRE_NOTHROW(copy.retract_to(1)); + REQUIRE_NOTHROW(copy.retract_to(0)); + REQUIRE_THROWS(copy.flush_to(1)); + + REQUIRE(copy.size() == 1); + REQUIRE(copy.root() == h0); +} + +TEST_CASE("SHA384 paths") +{ + const size_t num_leaves = 64; + auto hashes = make_hashesT<48>(num_leaves); + + merkle::Tree384 tree; + for (auto& h : hashes) + tree.insert(h); + auto root = tree.root(); + + for (size_t i = 0; i < num_leaves; i++) + { + auto path = tree.path(i); + REQUIRE(path->verify(root)); + std::vector serialised_path; + path->serialise(serialised_path); + REQUIRE(path->serialised_size() == serialised_path.size()); + } +} + +TEST_CASE("SHA512 empty tree") +{ + merkle::Tree512 tree; + + REQUIRE(tree.empty()); + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 0); + + REQUIRE_THROWS(tree.root()); + REQUIRE_THROWS(tree.path(0)); + REQUIRE_THROWS(tree.past_root(0)); + REQUIRE_THROWS(tree.past_path(0, 1)); + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.retract_to(0)); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + REQUIRE_NOTHROW(merkle::Tree512 dt(buffer)); +} + +TEST_CASE("SHA512 one-node tree") +{ + merkle::Tree512::Hash h; + merkle::Tree512 tree(h); + + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 0); + + REQUIRE(tree.root() == h); + REQUIRE(tree.leaf(0) == h); + REQUIRE(*tree.path(0)->root() == h); + REQUIRE(*tree.past_root(0) == h); + REQUIRE_THROWS(tree.past_root(1)); + REQUIRE(*tree.past_path(0, 0)->root() == h); + REQUIRE_THROWS(tree.past_path(0, 1)); + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.retract_to(0)); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + merkle::Tree512 dt(buffer); + REQUIRE(dt.root() == tree.root()); +} + +TEST_CASE("SHA512 three-node tree") +{ + merkle::Tree512::Hash h0, h1, hr; + h1.bytes[63] = 1; + + merkle::Tree512 tree; + + REQUIRE_NOTHROW(tree.insert(h0)); + REQUIRE_NOTHROW(tree.insert(h1)); + + hr = tree.root(); + + REQUIRE(tree.min_index() == 0); + REQUIRE(tree.max_index() == 1); + + REQUIRE(tree.leaf(0) == h0); + REQUIRE(tree.leaf(1) == h1); + REQUIRE(*tree.path(0)->root() == hr); + REQUIRE(*tree.past_root(0) == h0); + REQUIRE(*tree.past_root(1) == hr); + + auto pp00 = tree.past_path(0, 0); + REQUIRE(pp00->size() == 0); + REQUIRE(pp00->leaf() == h0); + REQUIRE(*pp00->root() == h0); + + auto pp01 = tree.past_path(0, 1); + REQUIRE(pp01->size() == 1); + REQUIRE(pp01->leaf() == h0); + REQUIRE((*pp01)[0] == h1); + REQUIRE(*pp01->root() == hr); + + std::vector buffer; + REQUIRE_NOTHROW(tree.serialise(buffer)); + merkle::Tree512 dt(buffer); + REQUIRE(dt.root() == tree.root()); + + merkle::Tree512 copy = tree; + + REQUIRE_NOTHROW(tree.flush_to(0)); + REQUIRE_NOTHROW(tree.flush_to(1)); + REQUIRE_THROWS(tree.retract_to(0)); + + REQUIRE_NOTHROW(copy.flush_to(0)); + REQUIRE_NOTHROW(copy.retract_to(1)); + REQUIRE_NOTHROW(copy.retract_to(0)); + REQUIRE_THROWS(copy.flush_to(1)); + + REQUIRE(copy.size() == 1); + REQUIRE(copy.root() == h0); +} + +TEST_CASE("SHA512 paths") +{ + const size_t num_leaves = 64; + auto hashes = make_hashesT<64>(num_leaves); + + merkle::Tree512 tree; + for (auto& h : hashes) + tree.insert(h); + auto root = tree.root(); + + for (size_t i = 0; i < num_leaves; i++) + { + auto path = tree.path(i); + REQUIRE(path->verify(root)); + std::vector serialised_path; + path->serialise(serialised_path); + REQUIRE(path->serialised_size() == serialised_path.size()); + } +} + +TEST_CASE("SHA256 vs SHA384 vs SHA512 produce different roots") +{ + merkle::Tree tree256; + merkle::Tree384 tree384; + merkle::Tree512 tree512; + + merkle::Hash h256; + merkle::Hash384 h384; + merkle::Hash512 h512; + + h256.bytes[0] = 42; + h384.bytes[0] = 42; + h512.bytes[0] = 42; + + tree256.insert(h256); + tree256.insert(h256); + tree384.insert(h384); + tree384.insert(h384); + tree512.insert(h512); + tree512.insert(h512); + + auto r256 = tree256.root(); + auto r384 = tree384.root(); + auto r512 = tree512.root(); + + REQUIRE(r256.size() == 32); + REQUIRE(r384.size() == 48); + REQUIRE(r512.size() == 64); +} +#endif \ No newline at end of file diff --git a/test/util.h b/test/util.h index d884a88..2abfc77 100644 --- a/test/util.h +++ b/test/util.h @@ -21,6 +21,21 @@ inline std::vector make_hashes(size_t n, size_t print_size = 3) return hashes; } +template +inline std::vector> make_hashesT( + size_t n, size_t print_size = 3) +{ + std::vector> hashes; + merkle::HashT h; + for (size_t i = 0; i < n; i++) + { + hashes.push_back(h); + for (size_t j = print_size - 1; ++h.bytes[j] == 0 && j != (size_t)-1; j--) + ; + } + return hashes; +} + inline size_t random_index(merkle::Tree& mt) { return (size_t)(