Skip to content

Commit

Permalink
Expand and test malleability functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
evoskuil committed Apr 11, 2024
1 parent 31ac522 commit d2d8a33
Show file tree
Hide file tree
Showing 7 changed files with 481 additions and 22 deletions.
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ test_libbitcoin_system_test_SOURCES = \
test/typelets.cpp \
test/types.cpp \
test/chain/block.cpp \
test/chain/block_malleable.cpp \
test/chain/chain_state.cpp \
test/chain/checkpoint.cpp \
test/chain/compact.cpp \
Expand Down
1 change: 1 addition & 0 deletions builds/cmake/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@ if (with-tests)
"../../test/typelets.cpp"
"../../test/types.cpp"
"../../test/chain/block.cpp"
"../../test/chain/block_malleable.cpp"
"../../test/chain/chain_state.cpp"
"../../test/chain/checkpoint.cpp"
"../../test/chain/compact.cpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
<ClCompile Include="..\..\..\..\test\chain\block.cpp">
<ObjectFileName>$(IntDir)test_chain_block.obj</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\..\..\test\chain\block_malleable.cpp" />
<ClCompile Include="..\..\..\..\test\chain\chain_state.cpp" />
<ClCompile Include="..\..\..\..\test\chain\checkpoint.cpp" />
<ClCompile Include="..\..\..\..\test\chain\compact.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
<ClCompile Include="..\..\..\..\test\chain\block.cpp">
<Filter>src\chain</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\chain\block_malleable.cpp">
<Filter>src\chain</Filter>
</ClCompile>
<ClCompile Include="..\..\..\..\test\chain\chain_state.cpp">
<Filter>src\chain</Filter>
</ClCompile>
Expand Down
23 changes: 20 additions & 3 deletions include/bitcoin/system/chain/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class BC_API block
public:
DEFAULT_COPY_MOVE_DESTRUCT(block);

typedef std_vector<size_t> sizes;
typedef std::shared_ptr<const block> cptr;

/// Constructors.
Expand Down Expand Up @@ -90,13 +91,16 @@ class BC_API block
uint64_t fees() const NOEXCEPT;
uint64_t claim() const NOEXCEPT;
hash_digest hash() const NOEXCEPT;
bool is_malleable() const NOEXCEPT;
bool is_malleable_duplicate() const NOEXCEPT;
bool is_malleable_coincident() const NOEXCEPT;
bool is_segregated() const NOEXCEPT;
size_t serialized_size(bool witness) const NOEXCEPT;
size_t signature_operations(bool bip16, bool bip141) const NOEXCEPT;

/// Computed malleation properties.
bool is_malleable() const NOEXCEPT;
bool is_malleable64() const NOEXCEPT;
bool is_malleable32() const NOEXCEPT;
bool is_malleated32() const NOEXCEPT;

/// Validation.
/// -----------------------------------------------------------------------

Expand All @@ -115,6 +119,19 @@ class BC_API block
block(const chain::header::cptr& header,
const chain::transactions_cptr& txs, bool valid) NOEXCEPT;

size_t malleated32_size() const NOEXCEPT;
bool is_malleated32(size_t width) const NOEXCEPT;
static constexpr bool is_malleable32_size(size_t set, size_t width) NOEXCEPT
{
// Malleable when set is odd at width depth and not before and not one.
// This is the only case in which Merkle clones the last item in a set.
for (auto depth = one; depth <= width; depth *= two, set = to_half(set))
if (is_odd(set))
return depth == width && !is_one(set);

return false;
}

/// Check (context free).
/// -----------------------------------------------------------------------

Expand Down
64 changes: 45 additions & 19 deletions src/chain/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -438,40 +438,66 @@ bool block::is_hash_limit_exceeded() const NOEXCEPT
return hashes.size() > hash_limit;
}

// This is not part of validation. Should be called after *invalidation* to
// determine if the invalidity is universal (otherwise do not cache invalid),
// and as an abbreviated validation when under checkpoint/milestone.
// lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html
bool block::is_malleable() const NOEXCEPT
{
return is_malleable_coincident() || is_malleable_duplicate();
return is_malleable64() || is_malleable32();
}

// Repeated tx hashes is a subset of is_internal_double_spend.
// This form of malleability also implies current block instance is invalid.
bool block::is_malleable_duplicate() const NOEXCEPT
bool block::is_malleable32() const NOEXCEPT
{
// A set is used to collapse duplicates.
std::set<hash_digest> hashes;
const auto unmalleated = txs_->size();
for (auto mally = one; mally <= unmalleated; mally *= two)
if (is_malleable32_size(unmalleated, mally))
return true;

BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT)
for (const auto& tx: *txs_)
hashes.insert(tx->hash(false));
BC_POP_WARNING()

return hashes.size() == txs_->size();
return false;
}

bool block::is_malleated32() const NOEXCEPT
{
return !is_zero(malleated32_size());
}

// protected
// The size of an actual malleation of this block, or zero.
size_t block::malleated32_size() const NOEXCEPT
{
const auto malleated = txs_->size();
for (auto mally = one; mally <= to_half(malleated); mally *= two)
if (is_malleable32_size(malleated - mally, mally) &&
is_malleated32(mally))
return mally;

return zero;
}

// protected
// True if the last width set of tx hashes repeats.
bool block::is_malleated32(size_t width) const NOEXCEPT
{
const auto malleated = txs_->size();
if (is_zero(width) || width > to_half(malleated))
return false;

auto mally = txs_->rbegin();
auto legit = std::next(mally, width);
while (!is_zero(width--))
if ((*mally++)->hash(false) != (*legit++)->hash(false))
return false;

return true;
}

// If all non-witness tx serializations are 64 bytes the id is malleable.
// This form of malleability does not imply current block instance is invalid.
bool block::is_malleable_coincident() const NOEXCEPT
bool block::is_malleable64() const NOEXCEPT
{
const auto two_leaf_size = [](const transaction::cptr& tx) NOEXCEPT
const auto two_leaves = [](const transaction::cptr& tx) NOEXCEPT
{
return tx->serialized_size(false) == two * hash_size;
};

return std::all_of(txs_->begin(), txs_->end(), two_leaf_size);
return !is_empty() && std::all_of(txs_->begin(), txs_->end(), two_leaves);
}

bool block::is_segregated() const NOEXCEPT
Expand Down
Loading

0 comments on commit d2d8a33

Please sign in to comment.