diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index 15ad80ce0b..90a9956dcb 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -90,6 +90,7 @@ 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_segregated() const NOEXCEPT; size_t serialized_size(bool witness) const NOEXCEPT; size_t signature_operations(bool bip16, bool bip141) const NOEXCEPT; diff --git a/include/bitcoin/system/error/block_error_t.hpp b/include/bitcoin/system/error/block_error_t.hpp index 59dd0607f8..ea7c0fdd94 100644 --- a/include/bitcoin/system/error/block_error_t.hpp +++ b/include/bitcoin/system/error/block_error_t.hpp @@ -55,6 +55,7 @@ enum block_error_t : uint8_t forward_reference, merkle_mismatch, block_legacy_sigop_limit, + malleable_identifier, // accept block block_non_final, diff --git a/src/chain/block.cpp b/src/chain/block.cpp index a592359523..42c40878e1 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -274,7 +274,12 @@ size_t block::serialized_size(bool witness) const NOEXCEPT // Connect. // ---------------------------------------------------------------------------- -////// Subset of is_internal_double_spend if sha256 collisions cannot happen. +// Subset of is_internal_double_spend if sha256 collisions cannot happen. This +// is because each tx must have an input and for there to be no double spend in +// the block the inputs must be unique (and only one coinbase). As the +// is_internal_double_spend check invalidates any block with duplicated txs, +// there can be no tx hash duplication within the merkle tree. And a block that +// fails block.check is not archived, and its header remains potentially valid. ////bool block::is_distinct_transaction_set() const ////{ //// // A set is used to collapse duplicates. @@ -450,6 +455,30 @@ 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). +// lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html +bool block::is_malleable() const NOEXCEPT +{ + // A two tx block cannot be malleable as coinbase is singular, otherwise + // if the last two non-witness tx hashes match then the id is malleable. + const auto count = txs_->size(); + if (count > two && is_even(count) && + txs_->at(sub1(count))->hash(false) == txs_->at(count)->hash(false)) + { + return true; + } + + // Hash of two same concatenated leaves is same as doubling one odd leaf. + const auto two_leaf_size = [](const transaction::cptr& tx) NOEXCEPT + { + return tx->serialized_size(false) == two * hash_size; + }; + + // If all non-witness tx serializations are 64 bytes the id is malleable. + return std::all_of(txs_->begin(), txs_->end(), two_leaf_size); +} + bool block::is_segregated() const NOEXCEPT { const auto segregated = [](const transaction::cptr& tx) NOEXCEPT @@ -666,8 +695,8 @@ code block::check() const NOEXCEPT { // context free. // empty_block is redundant with first_not_coinbase. - ////if (is_empty()) - //// return error::empty_block; + //if (is_empty()) + // return error::empty_block; if (is_oversized()) return error::block_size_limit; if (is_first_non_coinbase()) diff --git a/src/error/block_error_t.cpp b/src/error/block_error_t.cpp index 456ee9e6e5..739cbd075a 100644 --- a/src/error/block_error_t.cpp +++ b/src/error/block_error_t.cpp @@ -49,6 +49,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(block_error) { forward_reference, "transactions out of order" }, { merkle_mismatch, "merkle root mismatch" }, { block_legacy_sigop_limit, "too many block legacy signature operations" }, + { malleable_identifier, "block identifier is malleable" }, // accept block { block_non_final, "block contains a non-final transaction" }, diff --git a/test/chain/block.cpp b/test/chain/block.cpp index 02e774eab5..de3d0e9de2 100644 --- a/test/chain/block.cpp +++ b/test/chain/block.cpp @@ -312,6 +312,7 @@ BOOST_AUTO_TEST_CASE(block__hash__default__matches_header_hash) BOOST_REQUIRE_EQUAL(instance.hash(), instance.header().hash()); } +// is_malleable // is_segregated // serialized_size diff --git a/test/error/block_error_t.cpp b/test/error/block_error_t.cpp index e6f9fffb8f..b55b4b54c1 100644 --- a/test/error/block_error_t.cpp +++ b/test/error/block_error_t.cpp @@ -172,6 +172,15 @@ BOOST_AUTO_TEST_CASE(block_error_t__code__block_legacy_sigop_limit__true_exected BOOST_REQUIRE_EQUAL(ec.message(), "too many block legacy signature operations"); } +BOOST_AUTO_TEST_CASE(block_error_t__code__malleable_identifier__true_exected_message) +{ + constexpr auto value = error::malleable_identifier; + const auto ec = code(value); + BOOST_REQUIRE(ec); + BOOST_REQUIRE(ec == value); + BOOST_REQUIRE_EQUAL(ec.message(), "block identifier is malleable"); +} + // accept block BOOST_AUTO_TEST_CASE(block_error_t__code__block_non_final__true_exected_message)