Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 74 additions & 11 deletions merklecpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,22 @@ namespace merkle
return r;
}

static inline size_t deserialise_size_t(
const std::vector<uint8_t>& bytes, size_t& index)
{
const auto value = deserialise_uint64_t(bytes, index);
if constexpr (
std::numeric_limits<size_t>::digits < // NOLINT(misc-redundant-expression)
std::numeric_limits<uint64_t>::digits)
{
if (value > std::numeric_limits<size_t>::max())
{
throw std::runtime_error("serialised value exceeds platform limits");
}
}
return static_cast<size_t>(value);
Comment thread
achamayou marked this conversation as resolved.
}

static inline bool decode_hex_digit(char c, uint8_t& value)
{
if ('0' <= c && c <= '9')
Expand Down Expand Up @@ -433,9 +449,9 @@ namespace merkle
MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::deserialise " << std::endl);
elements.clear();
_leaf.deserialise(bytes, position);
_leaf_index = deserialise_uint64_t(bytes, position);
_max_index = deserialise_uint64_t(bytes, position);
size_t const num_elements = deserialise_uint64_t(bytes, position);
_leaf_index = deserialise_size_t(bytes, position);
_max_index = deserialise_size_t(bytes, position);
size_t const num_elements = deserialise_size_t(bytes, position);
for (size_t i = 0; i < num_elements; i++)
{
HashT<HASH_SIZE> hash(bytes, position);
Expand Down Expand Up @@ -610,6 +626,19 @@ namespace merkle
return r;
}

/// @brief Constructs a new tree node
/// @param hash The hash to move into the node
static Node* make(HashT<HASH_SIZE>&& hash)
{
auto r = new Node();
r->left = r->right = nullptr;
r->hash = std::move(hash);
r->dirty = false;
r->update_sizes();
assert(r->invariant());
return r;
}

/// @brief Constructs a new tree node
/// @param left The left child of the new node
/// @param right The right child of the new node
Expand Down Expand Up @@ -710,13 +739,19 @@ namespace merkle
{
return false;
}
const size_t max_size = height == size_digits ?
std::numeric_limits<size_t>::max() :
(size_t{1} << height) - 1;
const size_t max_size = full_size(height);
assert(size <= max_size);
return size == max_size;
}

static size_t full_size(uint8_t height)
{
constexpr size_t size_digits = std::numeric_limits<size_t>::digits;
assert(height <= size_digits);
return height == size_digits ? std::numeric_limits<size_t>::max() :
(size_t{1} << height) - 1;
}

/// @brief Updates the tree size and height of the subtree under a node
void update_sizes()
{
Expand Down Expand Up @@ -1523,14 +1558,42 @@ namespace merkle

clear();

size_t num_leaf_nodes = deserialise_uint64_t(bytes, position);
num_flushed = deserialise_uint64_t(bytes, position);
const size_t num_leaf_nodes = deserialise_size_t(bytes, position);
const size_t deserialised_num_flushed =
deserialise_size_t(bytes, position);

if (num_leaf_nodes == 0 && deserialised_num_flushed != 0)
{
throw std::runtime_error("serialised tree has no retained leaves");
}

// A binary tree has 2 * leaves - 1 nodes, which must fit in Node::size.
constexpr size_t max_num_leaves =
std::numeric_limits<size_t>::max() / 2 + 1;
if (
deserialised_num_flushed > max_num_leaves ||
num_leaf_nodes > max_num_leaves - deserialised_num_flushed)
{
throw std::runtime_error("serialised tree exceeds platform limits");
}

size_t num_hashes = num_leaf_nodes;
for (size_t it = deserialised_num_flushed; it != 0; it >>= 1)
{
num_hashes += it & 0x01;
}
if (
position > bytes.size() ||
num_hashes > (bytes.size() - position) / HASH_SIZE)
{
throw std::runtime_error("not enough bytes");
}

num_flushed = deserialised_num_flushed;
leaf_nodes.reserve(num_leaf_nodes);
for (size_t i = 0; i < num_leaf_nodes; i++)
{
Node* n = Node::make(bytes.data() + position);
position += HASH_SIZE;
Node* n = Node::make(Hash(bytes, position));
leaf_nodes.push_back(n);
}

Expand All @@ -1547,7 +1610,7 @@ namespace merkle
MERKLECPP_TRACE(MERKLECPP_TOUT << "+";);
auto n = Node::make(h);
n->height = level_no + 1;
n->size = (1 << n->height) - 1;
n->size = Node::full_size(n->height);
assert(n->invariant());
level.insert(level.begin(), n);
}
Expand Down
85 changes: 85 additions & 0 deletions test/unit_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,91 @@ TEST_CASE("Empty tree")
REQUIRE_NOTHROW(merkle::Tree dt(buffer)); // NOLINT(misc-const-correctness)
}

TEST_CASE("TreeT rejects invalid serialised leaf data")
{
for (size_t size = 0; size < 2 * sizeof(uint64_t); size++)
{
CAPTURE(size);
const std::vector<uint8_t> truncated_header(size, 0);
REQUIRE_THROWS_AS(
(void)merkle::Tree(truncated_header), std::out_of_range);
}

std::vector<uint8_t> excessive_count;
merkle::serialise_uint64_t(
std::numeric_limits<uint64_t>::max(), excessive_count);
merkle::serialise_uint64_t(0, excessive_count);
REQUIRE_THROWS_AS(
(void)merkle::Tree(excessive_count), std::runtime_error);

std::vector<uint8_t> truncated_hashes;
merkle::serialise_uint64_t(2, truncated_hashes);
merkle::serialise_uint64_t(0, truncated_hashes);
truncated_hashes.resize(
truncated_hashes.size() + merkle::Hash::size_bytes);
REQUIRE_THROWS_WITH_AS(
(void)merkle::Tree(truncated_hashes),
"not enough bytes",
std::runtime_error);

std::vector<uint8_t> no_retained_leaves;
merkle::serialise_uint64_t(0, no_retained_leaves);
merkle::serialise_uint64_t(1, no_retained_leaves);
no_retained_leaves.resize(
no_retained_leaves.size() + merkle::Hash::size_bytes);
REQUIRE_THROWS_WITH_AS(
(void)merkle::Tree(no_retained_leaves),
"serialised tree has no retained leaves",
std::runtime_error);

std::vector<uint8_t> overflowing_leaf_count;
merkle::serialise_uint64_t(1, overflowing_leaf_count);
merkle::serialise_uint64_t(
std::numeric_limits<size_t>::max(), overflowing_leaf_count);
overflowing_leaf_count.resize(
overflowing_leaf_count.size() + merkle::Hash::size_bytes);
REQUIRE_THROWS_WITH_AS(
(void)merkle::Tree(overflowing_leaf_count),
"serialised tree exceeds platform limits",
std::runtime_error);

std::vector<uint8_t> unrepresentable_tree_size;
merkle::serialise_uint64_t(1, unrepresentable_tree_size);
merkle::serialise_uint64_t(
std::numeric_limits<size_t>::max() / 2 + 1, unrepresentable_tree_size);
REQUIRE_THROWS_WITH_AS(
(void)merkle::Tree(unrepresentable_tree_size),
"serialised tree exceeds platform limits",
std::runtime_error);

std::vector<uint8_t> truncated_extra_hash;
merkle::serialise_uint64_t(1, truncated_extra_hash);
merkle::serialise_uint64_t(1, truncated_extra_hash);
truncated_extra_hash.resize(
truncated_extra_hash.size() + merkle::Hash::size_bytes);
REQUIRE_THROWS_WITH_AS(
(void)merkle::Tree(truncated_extra_hash),
"not enough bytes",
std::runtime_error);
}

TEST_CASE("TreeT deserialises flushed counts beyond signed shift width")
{
constexpr auto flushed_bit = std::numeric_limits<int>::digits;
if constexpr (flushed_bit + 1 < std::numeric_limits<size_t>::digits)
{
const size_t num_flushed = size_t{1} << flushed_bit;
std::vector<uint8_t> bytes;
merkle::serialise_uint64_t(1, bytes);
merkle::serialise_uint64_t(num_flushed, bytes);
bytes.resize(bytes.size() + 2 * merkle::Hash::size_bytes);

merkle::Tree tree(bytes);
REQUIRE(tree.num_leaves() == num_flushed + 1);
REQUIRE(tree.size() == 2 * num_flushed + 1);
}
}

TEST_CASE("One-node tree")
{
merkle::Tree::Hash h;
Expand Down
Loading