Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand and test malleability functions. #1434

Merged
merged 2 commits into from
Apr 11, 2024
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
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
2 changes: 2 additions & 0 deletions include/bitcoin/system/unicode/code_points.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <vector>
#include <bitcoin/system/define.hpp>

// TODO: these are static initializations.

namespace libbitcoin {
namespace system {

Expand Down
2 changes: 2 additions & 0 deletions include/bitcoin/system/wallet/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ namespace ctx
}
}

// TODO: these are static initializations.

/// Predefined contexts for use as default parameter values.
extern const context btc_mainnet_p2kh;
extern const context btc_mainnet_p2sh;
Expand Down
2 changes: 2 additions & 0 deletions include/bitcoin/system/words/catalogs/electrum.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ namespace electrum {

typedef words::dictionary<2048> catalog;

// TODO: these are static initializations.

extern const catalog::words& en;
extern const catalog::words& es;
extern const catalog::words& it;
Expand Down
2 changes: 2 additions & 0 deletions include/bitcoin/system/words/catalogs/mnemonic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ namespace mnemonic {

typedef words::dictionary<2048> catalog;

// TODO: these are static initializations.

extern const catalog::words en;
extern const catalog::words es;
extern const catalog::words it;
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