diff --git a/src/merkle.cpp b/src/merkle.cpp index 7c781ae..38f136a 100644 --- a/src/merkle.cpp +++ b/src/merkle.cpp @@ -57,4 +57,62 @@ void merkle_hash(const std::vector& hashes, hash& root) } } +void merkle_hash_full_tree(const std::vector& hashes, std::vector>& tree) +{ + const size_t count = hashes.size(); + const uint8_t* h = hashes[0].h; + + tree.clear(); + + if (count == 1) { + tree.push_back(hashes); + } + else if (count == 2) { + hash tmp; + keccak(h, HASH_SIZE * 2, tmp.h); + + tree.reserve(2); + tree.push_back(hashes); + tree.emplace_back(1, tmp); + } + else { + size_t cnt = 1, height = 1; + do { + cnt <<= 1; + ++height; + } while (cnt <= count); + cnt >>= 1; + + tree.reserve(height); + tree.push_back(hashes); + + tree.emplace_back(cnt); + { + std::vector& cur = tree.back(); + + const size_t k = cnt * 2 - count; + memcpy(cur.data(), h, k * HASH_SIZE); + + for (size_t i = k, j = k; j < cnt; i += 2, ++j) { + keccak(h + i * HASH_SIZE, HASH_SIZE * 2, cur[j].h); + } + } + + while (cnt > 1) { + cnt >>= 1; + + tree.emplace_back(cnt); + + const std::vector& prev = tree[tree.size() - 2]; + std::vector& cur = tree[tree.size() - 1]; + + cur.resize(cnt); + + for (size_t i = 0, j = 0; j < cnt; i += 2, ++j) { + keccak(prev[i].h, HASH_SIZE * 2, cur[j].h); + } + } + } +} + } // namespace p2pool diff --git a/src/merkle.h b/src/merkle.h index 1bf6f8d..a594121 100644 --- a/src/merkle.h +++ b/src/merkle.h @@ -20,5 +20,6 @@ namespace p2pool { void merkle_hash(const std::vector& hashes, hash& root); +void merkle_hash_full_tree(const std::vector& hashes, std::vector>& tree); } // namespace p2pool diff --git a/tests/src/merkle_tests.cpp b/tests/src/merkle_tests.cpp index 8ef709f..97e32ef 100644 --- a/tests/src/merkle_tests.cpp +++ b/tests/src/merkle_tests.cpp @@ -24,19 +24,57 @@ namespace p2pool { TEST(merkle, root_hash) { - hash input[5]; + hash input[10]; uint8_t data[] = "data 0"; - for (size_t i = 0; i < 5; ++i, ++data[sizeof(data) - 2]) { + for (size_t i = 0; i < 10; ++i, ++data[sizeof(data) - 2]) { keccak(data, sizeof(data) - 1, input[i].h); } hash root; std::vector hashes(1, input[0]); + auto check_full_tree = [&hashes, &root]() { + std::vector> tree; + merkle_hash_full_tree(hashes, tree); + + ASSERT_GE(tree.size(), 1); + + const std::vector& tree_root = tree.back(); + ASSERT_EQ(tree_root.size(), 1); + ASSERT_EQ(tree_root[0], root); + + ASSERT_EQ(tree[0], hashes); + + if (tree.size() > 1) { + ASSERT_LE(tree[1].size(), hashes.size()); + ASSERT_GE(tree[1].size() * 2, hashes.size()); + + const size_t spill_size = tree[1].size() * 2 - hashes.size(); + for (size_t i = 0; i < spill_size; ++i) { + ASSERT_EQ(tree[1][i], hashes[i]); + } + for (size_t i = spill_size, j = spill_size; i < tree[1].size(); ++i, j += 2) { + hash tmp; + keccak(hashes[j].h, HASH_SIZE * 2, tmp.h); + ASSERT_EQ(tmp, tree[1][i]); + } + } + + for (size_t i = tree.size() - 1; i > 1; --i) { + ASSERT_EQ(tree[i].size() * 2, tree[i - 1].size()); + for (size_t j = 0; j < tree[i].size(); ++j) { + hash tmp; + keccak(tree[i - 1][j * 2].h, HASH_SIZE * 2, tmp.h); + ASSERT_EQ(tmp, tree[i][j]); + } + } + }; + // 1 leaf merkle_hash(hashes, root); ASSERT_EQ(root, input[0]); + check_full_tree(); // 2 leaves hashes.push_back(input[1]); @@ -45,6 +83,7 @@ TEST(merkle, root_hash) hash check[8]; keccak(input[0].h, HASH_SIZE * 2, check[0].h); ASSERT_EQ(root, check[0]); + check_full_tree(); // 3 leaves hashes.push_back(input[2]); @@ -54,6 +93,7 @@ TEST(merkle, root_hash) check[0] = input[0]; keccak(check[0].h, HASH_SIZE * 2, check[0].h); ASSERT_EQ(root, check[0]); + check_full_tree(); // 4 leaves hashes.push_back(input[3]); @@ -63,6 +103,7 @@ TEST(merkle, root_hash) keccak(input[2].h, HASH_SIZE * 2, check[1].h); keccak(check[0].h, HASH_SIZE * 2, check[0].h); ASSERT_EQ(root, check[0]); + check_full_tree(); // 5 leaves hashes.push_back(input[4]); @@ -76,6 +117,94 @@ TEST(merkle, root_hash) keccak(check[2].h, HASH_SIZE * 2, check[1].h); keccak(check[0].h, HASH_SIZE * 2, check[0].h); ASSERT_EQ(root, check[0]); + check_full_tree(); + + // 6 leaves + hashes.push_back(input[5]); + merkle_hash(hashes, root); + + check[0] = input[0]; + check[1] = input[1]; + keccak(input[2].h, HASH_SIZE * 2, check[2].h); + keccak(input[4].h, HASH_SIZE * 2, check[3].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + ASSERT_EQ(root, check[0]); + check_full_tree(); + + // 7 leaves + hashes.push_back(input[6]); + merkle_hash(hashes, root); + + check[0] = input[0]; + keccak(input[1].h, HASH_SIZE * 2, check[1].h); + keccak(input[3].h, HASH_SIZE * 2, check[2].h); + keccak(input[5].h, HASH_SIZE * 2, check[3].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + ASSERT_EQ(root, check[0]); + check_full_tree(); + + // 8 leaves + hashes.push_back(input[7]); + merkle_hash(hashes, root); + + keccak(input[0].h, HASH_SIZE * 2, check[0].h); + keccak(input[2].h, HASH_SIZE * 2, check[1].h); + keccak(input[4].h, HASH_SIZE * 2, check[2].h); + keccak(input[6].h, HASH_SIZE * 2, check[3].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + ASSERT_EQ(root, check[0]); + check_full_tree(); + + // 9 leaves + hashes.push_back(input[8]); + merkle_hash(hashes, root); + + for (size_t i = 0; i < 7; ++i) { + check[i] = input[i]; + } + keccak(input[7].h, HASH_SIZE * 2, check[7].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + keccak(check[4].h, HASH_SIZE * 2, check[2].h); + keccak(check[6].h, HASH_SIZE * 2, check[3].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + + ASSERT_EQ(root, check[0]); + check_full_tree(); + + // 10 leaves + hashes.push_back(input[9]); + merkle_hash(hashes, root); + + for (size_t i = 0; i < 6; ++i) { + check[i] = input[i]; + } + keccak(input[6].h, HASH_SIZE * 2, check[6].h); + keccak(input[8].h, HASH_SIZE * 2, check[7].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + keccak(check[4].h, HASH_SIZE * 2, check[2].h); + keccak(check[6].h, HASH_SIZE * 2, check[3].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + keccak(check[2].h, HASH_SIZE * 2, check[1].h); + + keccak(check[0].h, HASH_SIZE * 2, check[0].h); + + ASSERT_EQ(root, check[0]); + check_full_tree(); } }