From 8f2e52516d8ec1af6a4c2f811900879d355f1a62 Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 5 Dec 2023 15:32:26 -0600 Subject: [PATCH] GH-1942 Add calculate_merkle that does not use left/right bit flip implementation --- .../chain/include/eosio/chain/merkle.hpp | 7 +++++ libraries/chain/merkle.cpp | 17 ++++++++++++ unittests/merkle_tree_tests.cpp | 27 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/eosio/chain/merkle.hpp b/libraries/chain/include/eosio/chain/merkle.hpp index a622ab7fbc..3d511d9900 100644 --- a/libraries/chain/include/eosio/chain/merkle.hpp +++ b/libraries/chain/include/eosio/chain/merkle.hpp @@ -16,7 +16,14 @@ namespace eosio { namespace chain { /** * Calculates the merkle root of a set of digests, if ids is odd it will duplicate the last id. + * Uses make_canonical_pair which before hashing sets the first bit of the previous hashes + * to 0 or 1 to indicate the side it is on. */ digest_type canonical_merkle( deque ids ); + /** + * Calculates the merkle root of a set of digests. Does not manipulate the digests. + */ + digest_type calculate_merkle( deque ids ); + } } /// eosio::chain diff --git a/libraries/chain/merkle.cpp b/libraries/chain/merkle.cpp index ece0e573cc..a7916353b7 100644 --- a/libraries/chain/merkle.cpp +++ b/libraries/chain/merkle.cpp @@ -49,4 +49,21 @@ digest_type canonical_merkle(deque ids) { return ids.front(); } +digest_type calculate_merkle( deque ids ) { + if( 0 == ids.size() ) { return digest_type(); } + + while( ids.size() > 1 ) { + if( ids.size() % 2 ) + ids.push_back(ids.back()); + + for (size_t i = 0; i < ids.size() / 2; ++i) { + ids[i] = digest_type::hash(std::make_pair(std::cref(ids[2 * i]), std::cref(ids[(2 * i) + 1]))); + } + + ids.resize(ids.size() / 2); + } + + return ids.front(); +} + } } // eosio::chain diff --git a/unittests/merkle_tree_tests.cpp b/unittests/merkle_tree_tests.cpp index a886caba5b..796c72ace4 100644 --- a/unittests/merkle_tree_tests.cpp +++ b/unittests/merkle_tree_tests.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -12,7 +13,8 @@ BOOST_AUTO_TEST_CASE(basic_append_and_root_check_canonical) { auto node1 = fc::sha256::hash("Node1"); tree.append(node1); - BOOST_CHECK_EQUAL(tree.get_root(), node1); + BOOST_CHECK_EQUAL(tree.get_root().str(), node1.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1}).str(), node1.str()); } BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { @@ -29,20 +31,26 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { tree.append(node1); BOOST_CHECK_EQUAL(tree.get_root().str(), node1.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1}).str(), node1.str()); tree.append(node2); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(make_canonical_pair(node1, node2)).str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2}).str(), fc::sha256::hash(make_canonical_pair(node1, node2)).str()); tree.append(node3); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(make_canonical_pair( fc::sha256::hash(make_canonical_pair(node1, node2)), fc::sha256::hash(make_canonical_pair(node3, node3)))).str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3}).str(), fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node3)))).str()); tree.append(node4); auto calculated_root = fc::sha256::hash(make_canonical_pair( fc::sha256::hash(make_canonical_pair(node1, node2)), fc::sha256::hash(make_canonical_pair(node3, node4)))); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4}).str(), calculated_root.str()); tree.append(node5); calculated_root = fc::sha256::hash( @@ -58,6 +66,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4, node5}).str(), calculated_root.str()); tree.append(node6); calculated_root = fc::sha256::hash( @@ -73,6 +82,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4, node5, node6}).str(), calculated_root.str()); tree.append(node7); calculated_root = fc::sha256::hash( @@ -88,6 +98,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4, node5, node6, node7}).str(), calculated_root.str()); tree.append(node8); calculated_root = fc::sha256::hash( @@ -103,6 +114,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4, node5, node6, node7, node8}).str(), calculated_root.str()); tree.append(node9); calculated_root = fc::sha256::hash(make_canonical_pair( @@ -131,6 +143,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { ) ) )); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(canonical_merkle({node1, node2, node3, node4, node5, node6, node7, node8, node9}).str(), calculated_root.str()); } BOOST_AUTO_TEST_CASE(basic_append_and_root_check) { @@ -140,6 +153,7 @@ BOOST_AUTO_TEST_CASE(basic_append_and_root_check) { auto node1 = fc::sha256::hash("Node1"); tree.append(node1); BOOST_CHECK_EQUAL(tree.get_root(), node1); + BOOST_CHECK_EQUAL(calculate_merkle({node1}).str(), node1.str()); } BOOST_AUTO_TEST_CASE(multiple_appends) { @@ -156,20 +170,26 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { tree.append(node1); BOOST_CHECK_EQUAL(tree.get_root().str(), node1.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1}).str(), node1.str()); tree.append(node2); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(std::make_pair(node1, node2)).str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2}).str(), fc::sha256::hash(std::make_pair(node1, node2)).str()); tree.append(node3); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(std::make_pair( fc::sha256::hash(std::make_pair(node1, node2)), fc::sha256::hash(std::make_pair(node3, node3)))).str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3}).str(), fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node3)))).str()); tree.append(node4); auto calculated_root = fc::sha256::hash(std::make_pair( fc::sha256::hash(std::make_pair(node1, node2)), fc::sha256::hash(std::make_pair(node3, node4)))); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4}).str(), calculated_root.str()); tree.append(node5); calculated_root = fc::sha256::hash( @@ -185,6 +205,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4, node5}).str(), calculated_root.str()); tree.append(node6); calculated_root = fc::sha256::hash( @@ -200,6 +221,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4, node5, node6}).str(), calculated_root.str()); tree.append(node7); calculated_root = fc::sha256::hash( @@ -215,6 +237,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4, node5, node6, node7}).str(), calculated_root.str()); tree.append(node8); calculated_root = fc::sha256::hash( @@ -230,6 +253,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { ) ); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4, node5, node6, node7, node8}).str(), calculated_root.str()); tree.append(node9); calculated_root = fc::sha256::hash(std::make_pair( @@ -258,6 +282,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { ) ) )); BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + BOOST_CHECK_EQUAL(calculate_merkle({node1, node2, node3, node4, node5, node6, node7, node8, node9}).str(), calculated_root.str()); } BOOST_AUTO_TEST_SUITE_END()