diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/crc32c/src/crc32c_arm64.cc | 32 | ||||
-rw-r--r-- | src/crypto/sha256.cpp | 8 | ||||
-rw-r--r-- | src/net_processing.cpp | 11 | ||||
-rw-r--r-- | src/primitives/block.h | 8 | ||||
-rw-r--r-- | src/serialize.h | 20 | ||||
-rw-r--r-- | src/test/fuzz/net.cpp | 37 | ||||
-rw-r--r-- | src/test/streams_tests.cpp | 9 | ||||
-rw-r--r-- | src/test/system_tests.cpp | 32 | ||||
-rw-r--r-- | src/test/validation_tests.cpp | 215 | ||||
-rw-r--r-- | src/util/transaction_identifier.h | 1 | ||||
-rw-r--r-- | src/validation.cpp | 154 | ||||
-rw-r--r-- | src/validation.h | 3 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 4 |
14 files changed, 433 insertions, 103 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 3e8870c828..3e24ea5a7e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -50,10 +50,8 @@ LIBBITCOIN_WALLET_TOOL=libbitcoin_wallet_tool.a endif LIBBITCOIN_CRYPTO = $(LIBBITCOIN_CRYPTO_BASE) -if USE_ASM LIBBITCOIN_CRYPTO_SSE4 = crypto/libbitcoin_crypto_sse4.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SSE4) -endif if ENABLE_SSE41 LIBBITCOIN_CRYPTO_SSE41 = crypto/libbitcoin_crypto_sse41.la LIBBITCOIN_CRYPTO += $(LIBBITCOIN_CRYPTO_SSE41) diff --git a/src/crc32c/src/crc32c_arm64.cc b/src/crc32c/src/crc32c_arm64.cc index 1da04ed34a..711616cd2f 100644 --- a/src/crc32c/src/crc32c_arm64.cc +++ b/src/crc32c/src/crc32c_arm64.cc @@ -12,6 +12,7 @@ #include <cstddef> #include <cstdint> +#include <cstring> #include "./crc32c_internal.h" #ifdef CRC32C_HAVE_CONFIG_H @@ -29,14 +30,14 @@ // compute 8bytes for each segment parallelly #define CRC32C32BYTES(P, IND) \ do { \ - crc1 = __crc32cd( \ - crc1, *((const uint64_t *)(P) + (SEGMENTBYTES / 8) * 1 + (IND))); \ - crc2 = __crc32cd( \ - crc2, *((const uint64_t *)(P) + (SEGMENTBYTES / 8) * 2 + (IND))); \ - crc3 = __crc32cd( \ - crc3, *((const uint64_t *)(P) + (SEGMENTBYTES / 8) * 3 + (IND))); \ - crc0 = __crc32cd( \ - crc0, *((const uint64_t *)(P) + (SEGMENTBYTES / 8) * 0 + (IND))); \ + std::memcpy(&d64, (P) + SEGMENTBYTES * 1 + (IND) * 8, sizeof(d64)); \ + crc1 = __crc32cd(crc1, d64); \ + std::memcpy(&d64, (P) + SEGMENTBYTES * 2 + (IND) * 8, sizeof(d64)); \ + crc2 = __crc32cd(crc2, d64); \ + std::memcpy(&d64, (P) + SEGMENTBYTES * 3 + (IND) * 8, sizeof(d64)); \ + crc3 = __crc32cd(crc3, d64); \ + std::memcpy(&d64, (P) + SEGMENTBYTES * 0 + (IND) * 8, sizeof(d64)); \ + crc0 = __crc32cd(crc0, d64); \ } while (0); // compute 8*8 bytes for each segment parallelly @@ -68,6 +69,9 @@ uint32_t ExtendArm64(uint32_t crc, const uint8_t *data, size_t size) { int64_t length = size; uint32_t crc0, crc1, crc2, crc3; uint64_t t0, t1, t2; + uint16_t d16; + uint32_t d32; + uint64_t d64; // k0=CRC(x^(3*SEGMENTBYTES*8)), k1=CRC(x^(2*SEGMENTBYTES*8)), // k2=CRC(x^(SEGMENTBYTES*8)) @@ -88,7 +92,8 @@ uint32_t ExtendArm64(uint32_t crc, const uint8_t *data, size_t size) { t2 = (uint64_t)vmull_p64(crc2, k2); t1 = (uint64_t)vmull_p64(crc1, k1); t0 = (uint64_t)vmull_p64(crc0, k0); - crc = __crc32cd(crc3, *(uint64_t *)data); + std::memcpy(&d64, data, sizeof(d64)); + crc = __crc32cd(crc3, d64); data += sizeof(uint64_t); crc ^= __crc32cd(0, t2); crc ^= __crc32cd(0, t1); @@ -98,18 +103,21 @@ uint32_t ExtendArm64(uint32_t crc, const uint8_t *data, size_t size) { } while (length >= 8) { - crc = __crc32cd(crc, *(uint64_t *)data); + std::memcpy(&d64, data, sizeof(d64)); + crc = __crc32cd(crc, d64); data += 8; length -= 8; } if (length & 4) { - crc = __crc32cw(crc, *(uint32_t *)data); + std::memcpy(&d32, data, sizeof(d32)); + crc = __crc32cw(crc, d32); data += 4; } if (length & 2) { - crc = __crc32ch(crc, *(uint16_t *)data); + std::memcpy(&d16, data, sizeof(d16)); + crc = __crc32ch(crc, d16); data += 2; } diff --git a/src/crypto/sha256.cpp b/src/crypto/sha256.cpp index 36ef6d9a1a..4c7bb6f20f 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -26,13 +26,11 @@ #endif #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) -#if defined(USE_ASM) namespace sha256_sse4 { void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks); } #endif -#endif namespace sha256d64_sse41 { @@ -574,7 +572,7 @@ bool SelfTest() { } #if !defined(DISABLE_OPTIMIZED_SHA256) -#if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) +#if (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) /** Check whether the OS has enabled AVX registers. */ bool AVXEnabled() { @@ -597,7 +595,7 @@ std::string SHA256AutoDetect(sha256_implementation::UseImplementation use_implem TransformD64_8way = nullptr; #if !defined(DISABLE_OPTIMIZED_SHA256) -#if defined(USE_ASM) && defined(HAVE_GETCPUID) +#if defined(HAVE_GETCPUID) bool have_sse4 = false; bool have_xsave = false; bool have_avx = false; @@ -654,7 +652,7 @@ std::string SHA256AutoDetect(sha256_implementation::UseImplementation use_implem ret += ",avx2(8way)"; } #endif -#endif // defined(USE_ASM) && defined(HAVE_GETCPUID) +#endif // defined(HAVE_GETCPUID) #if defined(ENABLE_ARM_SHANI) bool have_arm_shani = false; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index c8da927763..5c3ec5f700 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4719,6 +4719,16 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, LogPrint(BCLog::NET, "received block %s peer=%d\n", pblock->GetHash().ToString(), pfrom.GetId()); + const CBlockIndex* prev_block{WITH_LOCK(m_chainman.GetMutex(), return m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock))}; + + if (IsBlockMutated(/*block=*/*pblock, + /*check_witness_root=*/DeploymentActiveAfter(prev_block, m_chainman, Consensus::DEPLOYMENT_SEGWIT))) { + LogDebug(BCLog::NET, "Received mutated block from peer=%d\n", peer->m_id); + Misbehaving(*peer, 100, "mutated block"); + WITH_LOCK(cs_main, RemoveBlockRequest(pblock->GetHash(), peer->m_id)); + return; + } + bool forceProcessing = false; const uint256 hash(pblock->GetHash()); bool min_pow_checked = false; @@ -4734,7 +4744,6 @@ void PeerManagerImpl::ProcessMessage(CNode& pfrom, const std::string& msg_type, mapBlockSource.emplace(hash, std::make_pair(pfrom.GetId(), true)); // Check work on this block against our anti-dos thresholds. - const CBlockIndex* prev_block = m_chainman.m_blockman.LookupBlockIndex(pblock->hashPrevBlock); if (prev_block && prev_block->nChainWork + CalculateHeadersWork({pblock->GetBlockHeader()}) >= GetAntiDoSWorkThreshold()) { min_pow_checked = true; } diff --git a/src/primitives/block.h b/src/primitives/block.h index 99accfc7dd..832f8a03f7 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -71,8 +71,10 @@ public: // network and disk std::vector<CTransactionRef> vtx; - // memory only - mutable bool fChecked; + // Memory-only flags for caching expensive checks + mutable bool fChecked; // CheckBlock() + mutable bool m_checked_witness_commitment{false}; // CheckWitnessCommitment() + mutable bool m_checked_merkle_root{false}; // CheckMerkleRoot() CBlock() { @@ -95,6 +97,8 @@ public: CBlockHeader::SetNull(); vtx.clear(); fChecked = false; + m_checked_witness_commitment = false; + m_checked_merkle_root = false; } CBlockHeader GetBlockHeader() const diff --git a/src/serialize.h b/src/serialize.h index 5ae701191c..2f13fba582 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -6,10 +6,6 @@ #ifndef BITCOIN_SERIALIZE_H #define BITCOIN_SERIALIZE_H -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - #include <attributes.h> #include <compat/assumptions.h> // IWYU pragma: keep #include <compat/endian.h> @@ -17,6 +13,7 @@ #include <span.h> #include <algorithm> +#include <concepts> #include <cstdint> #include <cstring> #include <ios> @@ -263,9 +260,14 @@ const Out& AsBase(const In& x) // i.e. anything that supports .read(Span<std::byte>) and .write(Span<const std::byte>) // // clang-format off -#ifndef CHAR_EQUALS_INT8 -template <typename Stream> void Serialize(Stream&, char) = delete; // char serialization forbidden. Use uint8_t or int8_t -#endif + +// Typically int8_t and char are distinct types, but some systems may define int8_t +// in terms of char. Forbid serialization of char in the typical case, but allow it if +// it's the only way to describe an int8_t. +template<class T> +concept CharNotInt8 = std::same_as<T, char> && !std::same_as<T, int8_t>; + +template <typename Stream, CharNotInt8 V> void Serialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template <typename Stream> void Serialize(Stream& s, std::byte a) { ser_writedata8(s, uint8_t(a)); } template<typename Stream> inline void Serialize(Stream& s, int8_t a ) { ser_writedata8(s, a); } template<typename Stream> inline void Serialize(Stream& s, uint8_t a ) { ser_writedata8(s, a); } @@ -279,9 +281,7 @@ template <typename Stream, BasicByte B, int N> void Serialize(Stream& s, const B template <typename Stream, BasicByte B, std::size_t N> void Serialize(Stream& s, const std::array<B, N>& a) { s.write(MakeByteSpan(a)); } template <typename Stream, BasicByte B> void Serialize(Stream& s, Span<B> span) { s.write(AsBytes(span)); } -#ifndef CHAR_EQUALS_INT8 -template <typename Stream> void Unserialize(Stream&, char) = delete; // char serialization forbidden. Use uint8_t or int8_t -#endif +template <typename Stream, CharNotInt8 V> void Unserialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template <typename Stream> void Unserialize(Stream& s, std::byte& a) { a = std::byte{ser_readdata8(s)}; } template<typename Stream> inline void Unserialize(Stream& s, int8_t& a ) { a = ser_readdata8(s); } template<typename Stream> inline void Unserialize(Stream& s, uint8_t& a ) { a = ser_readdata8(s); } diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index c882bd766a..e8b1480c5b 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -77,3 +77,40 @@ FUZZ_TARGET(net, .init = initialize_net) (void)node.HasPermission(net_permission_flags); (void)node.ConnectedThroughNetwork(); } + +FUZZ_TARGET(local_address, .init = initialize_net) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + CService service{ConsumeService(fuzzed_data_provider)}; + CNode node{ConsumeNode(fuzzed_data_provider)}; + { + LOCK(g_maplocalhost_mutex); + mapLocalHost.clear(); + } + LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000) { + CallOneOf( + fuzzed_data_provider, + [&] { + service = ConsumeService(fuzzed_data_provider); + }, + [&] { + const bool added{AddLocal(service, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, LOCAL_MAX - 1))}; + if (!added) return; + assert(service.IsRoutable()); + assert(IsLocal(service)); + assert(SeenLocal(service)); + }, + [&] { + (void)RemoveLocal(service); + }, + [&] { + (void)SeenLocal(service); + }, + [&] { + (void)IsLocal(service); + }, + [&] { + (void)GetLocalAddress(node); + }); + } +} diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 7d1ac5a19a..0903f987f6 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -29,7 +29,14 @@ BOOST_AUTO_TEST_CASE(xor_file) BOOST_CHECK_EXCEPTION(xor_file.ignore(1), std::ios_base::failure, HasReason{"AutoFile::ignore: file handle is nullpt"}); } { - AutoFile xor_file{raw_file("wbx"), xor_pat}; +#ifdef __MINGW64__ + // Our usage of mingw-w64 and the msvcrt runtime does not support + // the x modifier for the _wfopen(). + const char* mode = "wb"; +#else + const char* mode = "wbx"; +#endif + AutoFile xor_file{raw_file(mode), xor_pat}; xor_file << test1 << test2; } { diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index 76a8f80ba1..90fce9adf9 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -29,23 +29,12 @@ BOOST_AUTO_TEST_CASE(dummy) BOOST_AUTO_TEST_CASE(run_command) { -#ifdef WIN32 - // https://www.winehq.org/pipermail/wine-devel/2008-September/069387.html - auto hntdll = GetModuleHandleA("ntdll.dll"); - assert(hntdll); - const bool wine_runtime = GetProcAddress(hntdll, "wine_get_version"); -#endif - { const UniValue result = RunCommandParseJSON(""); BOOST_CHECK(result.isNull()); } { -#ifdef WIN32 - const UniValue result = RunCommandParseJSON("cmd.exe /c echo {\"success\": true}"); -#else const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\""); -#endif BOOST_CHECK(result.isObject()); const UniValue& success = result.find_value("success"); BOOST_CHECK(!success.isNull()); @@ -53,11 +42,7 @@ BOOST_AUTO_TEST_CASE(run_command) } { // An invalid command is handled by Boost -#ifdef WIN32 - const int expected_error{wine_runtime ? 6 : 2}; -#else const int expected_error{2}; -#endif BOOST_CHECK_EXCEPTION(RunCommandParseJSON("invalid_command"), boost::process::process_error, [&](const boost::process::process_error& e) { BOOST_CHECK(std::string(e.what()).find("RunCommandParseJSON error:") == std::string::npos); BOOST_CHECK_EQUAL(e.code().value(), expected_error); @@ -66,11 +51,7 @@ BOOST_AUTO_TEST_CASE(run_command) } { // Return non-zero exit code, no output to stderr -#ifdef WIN32 - const std::string command{"cmd.exe /c exit 1"}; -#else const std::string command{"false"}; -#endif BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) { const std::string what{e.what()}; BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned 1: \n", command)) != std::string::npos); @@ -79,13 +60,8 @@ BOOST_AUTO_TEST_CASE(run_command) } { // Return non-zero exit code, with error message for stderr -#ifdef WIN32 - const std::string command{"cmd.exe /c dir nosuchfile"}; - const std::string expected{wine_runtime ? "File not found." : "File Not Found"}; -#else const std::string command{"ls nosuchfile"}; const std::string expected{"No such file or directory"}; -#endif BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, [&](const std::runtime_error& e) { const std::string what(e.what()); BOOST_CHECK(what.find(strprintf("RunCommandParseJSON error: process(%s) returned", command)) != std::string::npos); @@ -95,15 +71,10 @@ BOOST_AUTO_TEST_CASE(run_command) } { // Unable to parse JSON -#ifdef WIN32 - const std::string command{"cmd.exe /c echo {"}; -#else const std::string command{"echo {"}; -#endif BOOST_CHECK_EXCEPTION(RunCommandParseJSON(command), std::runtime_error, HasReason("Unable to parse JSON: {")); } - // Test std::in, except for Windows -#ifndef WIN32 + // Test std::in { const UniValue result = RunCommandParseJSON("cat", "{\"success\": true}"); BOOST_CHECK(result.isObject()); @@ -111,7 +82,6 @@ BOOST_AUTO_TEST_CASE(run_command) BOOST_CHECK(!success.isNull()); BOOST_CHECK_EQUAL(success.get_bool(), true); } -#endif } #endif // ENABLE_EXTERNAL_SIGNER diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index 14440571eb..93a884be6d 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -4,12 +4,17 @@ #include <chainparams.h> #include <consensus/amount.h> +#include <consensus/merkle.h> +#include <core_io.h> +#include <hash.h> #include <net.h> #include <signet.h> #include <uint256.h> #include <util/chaintype.h> #include <validation.h> +#include <string> + #include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> @@ -145,4 +150,214 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo) BOOST_CHECK_EQUAL(out110_2.nChainTx, 111U); } +BOOST_AUTO_TEST_CASE(block_malleation) +{ + // Test utilities that calls `IsBlockMutated` and then clears the validity + // cache flags on `CBlock`. + auto is_mutated = [](CBlock& block, bool check_witness_root) { + bool mutated{IsBlockMutated(block, check_witness_root)}; + block.fChecked = false; + block.m_checked_witness_commitment = false; + block.m_checked_merkle_root = false; + return mutated; + }; + auto is_not_mutated = [&is_mutated](CBlock& block, bool check_witness_root) { + return !is_mutated(block, check_witness_root); + }; + + // Test utilities to create coinbase transactions and insert witness + // commitments. + // + // Note: this will not include the witness stack by default to avoid + // triggering the "no witnesses allowed for blocks that don't commit to + // witnesses" rule when testing other malleation vectors. + auto create_coinbase_tx = [](bool include_witness = false) { + CMutableTransaction coinbase; + coinbase.vin.resize(1); + if (include_witness) { + coinbase.vin[0].scriptWitness.stack.resize(1); + coinbase.vin[0].scriptWitness.stack[0] = std::vector<unsigned char>(32, 0x00); + } + + coinbase.vout.resize(1); + coinbase.vout[0].scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); + coinbase.vout[0].scriptPubKey[0] = OP_RETURN; + coinbase.vout[0].scriptPubKey[1] = 0x24; + coinbase.vout[0].scriptPubKey[2] = 0xaa; + coinbase.vout[0].scriptPubKey[3] = 0x21; + coinbase.vout[0].scriptPubKey[4] = 0xa9; + coinbase.vout[0].scriptPubKey[5] = 0xed; + + auto tx = MakeTransactionRef(coinbase); + assert(tx->IsCoinBase()); + return tx; + }; + auto insert_witness_commitment = [](CBlock& block, uint256 commitment) { + assert(!block.vtx.empty() && block.vtx[0]->IsCoinBase() && !block.vtx[0]->vout.empty()); + + CMutableTransaction mtx{*block.vtx[0]}; + CHash256().Write(commitment).Write(std::vector<unsigned char>(32, 0x00)).Finalize(commitment); + memcpy(&mtx.vout[0].scriptPubKey[6], commitment.begin(), 32); + block.vtx[0] = MakeTransactionRef(mtx); + }; + + { + CBlock block; + + // Empty block is expected to have merkle root of 0x0. + BOOST_CHECK(block.vtx.empty()); + block.hashMerkleRoot = uint256{1}; + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + block.hashMerkleRoot = uint256{}; + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false)); + + // Block with a single coinbase tx is mutated if the merkle root is not + // equal to the coinbase tx's hash. + block.vtx.push_back(create_coinbase_tx()); + BOOST_CHECK(block.vtx[0]->GetHash() != block.hashMerkleRoot); + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + block.hashMerkleRoot = block.vtx[0]->GetHash(); + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false)); + + // Block with two transactions is mutated if the merkle root does not + // match the double sha256 of the concatenation of the two transaction + // hashes. + block.vtx.push_back(MakeTransactionRef(CMutableTransaction{})); + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + HashWriter hasher; + hasher.write(block.vtx[0]->GetHash()); + hasher.write(block.vtx[1]->GetHash()); + block.hashMerkleRoot = hasher.GetHash(); + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false)); + + // Block with two transactions is mutated if any node is duplicate. + { + block.vtx[1] = block.vtx[0]; + HashWriter hasher; + hasher.write(block.vtx[0]->GetHash()); + hasher.write(block.vtx[1]->GetHash()); + block.hashMerkleRoot = hasher.GetHash(); + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + } + + // Blocks with 64-byte coinbase transactions are not considered mutated + block.vtx.clear(); + { + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vout.resize(1); + mtx.vout[0].scriptPubKey.resize(4); + block.vtx.push_back(MakeTransactionRef(mtx)); + block.hashMerkleRoot = block.vtx.back()->GetHash(); + assert(block.vtx.back()->IsCoinBase()); + assert(GetSerializeSize(TX_NO_WITNESS(block.vtx.back())) == 64); + } + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false)); + } + + { + // Test merkle root malleation + + // Pseudo code to mine transactions tx{1,2,3}: + // + // ``` + // loop { + // tx1 = random_tx() + // tx2 = random_tx() + // tx3 = deserialize_tx(txid(tx1) || txid(tx2)); + // if serialized_size_without_witness(tx3) == 64 { + // print(hex(tx3)) + // break + // } + // } + // ``` + // + // The `random_tx` function used to mine the txs below simply created + // empty transactions with a random version field. + CMutableTransaction tx1; + BOOST_CHECK(DecodeHexTx(tx1, "ff204bd0000000000000", /*try_no_witness=*/true, /*try_witness=*/false)); + CMutableTransaction tx2; + BOOST_CHECK(DecodeHexTx(tx2, "8ae53c92000000000000", /*try_no_witness=*/true, /*try_witness=*/false)); + CMutableTransaction tx3; + BOOST_CHECK(DecodeHexTx(tx3, "cdaf22d00002c6a7f848f8ae4d30054e61dcf3303d6fe01d282163341f06feecc10032b3160fcab87bdfe3ecfb769206ef2d991b92f8a268e423a6ef4d485f06", /*try_no_witness=*/true, /*try_witness=*/false)); + { + // Verify that double_sha256(txid1||txid2) == txid3 + HashWriter hasher; + hasher.write(tx1.GetHash()); + hasher.write(tx2.GetHash()); + assert(hasher.GetHash() == tx3.GetHash()); + // Verify that tx3 is 64 bytes in size (without witness). + assert(GetSerializeSize(TX_NO_WITNESS(tx3)) == 64); + } + + CBlock block; + block.vtx.push_back(MakeTransactionRef(tx1)); + block.vtx.push_back(MakeTransactionRef(tx2)); + uint256 merkle_root = block.hashMerkleRoot = BlockMerkleRoot(block); + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/false)); + + // Mutate the block by replacing the two transactions with one 64-byte + // transaction that serializes into the concatenation of the txids of + // the transactions in the unmutated block. + block.vtx.clear(); + block.vtx.push_back(MakeTransactionRef(tx3)); + BOOST_CHECK(!block.vtx.back()->IsCoinBase()); + BOOST_CHECK(BlockMerkleRoot(block) == merkle_root); + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + } + + { + CBlock block; + block.vtx.push_back(create_coinbase_tx(/*include_witness=*/true)); + { + CMutableTransaction mtx; + mtx.vin.resize(1); + mtx.vin[0].scriptWitness.stack.resize(1); + mtx.vin[0].scriptWitness.stack[0] = {0}; + block.vtx.push_back(MakeTransactionRef(mtx)); + } + block.hashMerkleRoot = BlockMerkleRoot(block); + // Block with witnesses is considered mutated if the witness commitment + // is not validated. + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/false)); + // Block with invalid witness commitment is considered mutated. + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true)); + + // Block with valid commitment is not mutated + { + auto commitment{BlockWitnessMerkleRoot(block)}; + insert_witness_commitment(block, commitment); + block.hashMerkleRoot = BlockMerkleRoot(block); + } + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/true)); + + // Malleating witnesses should be caught by `IsBlockMutated`. + { + CMutableTransaction mtx{*block.vtx[1]}; + assert(!mtx.vin[0].scriptWitness.stack[0].empty()); + ++mtx.vin[0].scriptWitness.stack[0][0]; + block.vtx[1] = MakeTransactionRef(mtx); + } + // Without also updating the witness commitment, the merkle root should + // not change when changing one of the witnesses. + BOOST_CHECK(block.hashMerkleRoot == BlockMerkleRoot(block)); + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true)); + { + auto commitment{BlockWitnessMerkleRoot(block)}; + insert_witness_commitment(block, commitment); + block.hashMerkleRoot = BlockMerkleRoot(block); + } + BOOST_CHECK(is_not_mutated(block, /*check_witness_root=*/true)); + + // Test malleating the coinbase witness reserved value + { + CMutableTransaction mtx{*block.vtx[0]}; + mtx.vin[0].scriptWitness.stack.resize(0); + block.vtx[0] = MakeTransactionRef(mtx); + block.hashMerkleRoot = BlockMerkleRoot(block); + } + BOOST_CHECK(is_mutated(block, /*check_witness_root=*/true)); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/transaction_identifier.h b/src/util/transaction_identifier.h index 89e10dee01..d4a0ede25a 100644 --- a/src/util/transaction_identifier.h +++ b/src/util/transaction_identifier.h @@ -44,6 +44,7 @@ public: constexpr void SetNull() { m_wrapped.SetNull(); } std::string GetHex() const { return m_wrapped.GetHex(); } std::string ToString() const { return m_wrapped.ToString(); } + static constexpr auto size() { return decltype(m_wrapped)::size(); } constexpr const std::byte* data() const { return reinterpret_cast<const std::byte*>(m_wrapped.data()); } constexpr const std::byte* begin() const { return reinterpret_cast<const std::byte*>(m_wrapped.begin()); } constexpr const std::byte* end() const { return reinterpret_cast<const std::byte*>(m_wrapped.end()); } diff --git a/src/validation.cpp b/src/validation.cpp index 81a3c35864..f8e1de55e9 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3662,6 +3662,87 @@ static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& st return true; } +static bool CheckMerkleRoot(const CBlock& block, BlockValidationState& state) +{ + if (block.m_checked_merkle_root) return true; + + bool mutated; + uint256 merkle_root = BlockMerkleRoot(block, &mutated); + if (block.hashMerkleRoot != merkle_root) { + return state.Invalid( + /*result=*/BlockValidationResult::BLOCK_MUTATED, + /*reject_reason=*/"bad-txnmrklroot", + /*debug_message=*/"hashMerkleRoot mismatch"); + } + + // Check for merkle tree malleability (CVE-2012-2459): repeating sequences + // of transactions in a block without affecting the merkle root of a block, + // while still invalidating it. + if (mutated) { + return state.Invalid( + /*result=*/BlockValidationResult::BLOCK_MUTATED, + /*reject_reason=*/"bad-txns-duplicate", + /*debug_message=*/"duplicate transaction"); + } + + block.m_checked_merkle_root = true; + return true; +} + +/** CheckWitnessMalleation performs checks for block malleation with regard to + * its witnesses. + * + * Note: If the witness commitment is expected (i.e. `expect_witness_commitment + * = true`), then the block is required to have at least one transaction and the + * first transaction needs to have at least one input. */ +static bool CheckWitnessMalleation(const CBlock& block, bool expect_witness_commitment, BlockValidationState& state) +{ + if (expect_witness_commitment) { + if (block.m_checked_witness_commitment) return true; + + int commitpos = GetWitnessCommitmentIndex(block); + if (commitpos != NO_WITNESS_COMMITMENT) { + assert(!block.vtx.empty() && !block.vtx[0]->vin.empty()); + const auto& witness_stack{block.vtx[0]->vin[0].scriptWitness.stack}; + + if (witness_stack.size() != 1 || witness_stack[0].size() != 32) { + return state.Invalid( + /*result=*/BlockValidationResult::BLOCK_MUTATED, + /*reject_reason=*/"bad-witness-nonce-size", + /*debug_message=*/strprintf("%s : invalid witness reserved value size", __func__)); + } + + // The malleation check is ignored; as the transaction tree itself + // already does not permit it, it is impossible to trigger in the + // witness tree. + uint256 hash_witness = BlockWitnessMerkleRoot(block, /*mutated=*/nullptr); + + CHash256().Write(hash_witness).Write(witness_stack[0]).Finalize(hash_witness); + if (memcmp(hash_witness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { + return state.Invalid( + /*result=*/BlockValidationResult::BLOCK_MUTATED, + /*reject_reason=*/"bad-witness-merkle-match", + /*debug_message=*/strprintf("%s : witness merkle commitment mismatch", __func__)); + } + + block.m_checked_witness_commitment = true; + return true; + } + } + + // No witness data is allowed in blocks that don't commit to witness data, as this would otherwise leave room for spam + for (const auto& tx : block.vtx) { + if (tx->HasWitness()) { + return state.Invalid( + /*result=*/BlockValidationResult::BLOCK_MUTATED, + /*reject_reason=*/"unexpected-witness", + /*debug_message=*/strprintf("%s : unexpected witness data found", __func__)); + } + } + + return true; +} + bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot) { // These are checks that are independent of context. @@ -3680,17 +3761,8 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu } // Check the merkle root. - if (fCheckMerkleRoot) { - bool mutated; - uint256 hashMerkleRoot2 = BlockMerkleRoot(block, &mutated); - if (block.hashMerkleRoot != hashMerkleRoot2) - return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txnmrklroot", "hashMerkleRoot mismatch"); - - // Check for merkle tree malleability (CVE-2012-2459): repeating sequences - // of transactions in a block without affecting the merkle root of a block, - // while still invalidating it. - if (mutated) - return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-txns-duplicate", "duplicate transaction"); + if (fCheckMerkleRoot && !CheckMerkleRoot(block, state)) { + return false; } // All potential-corruption validation must be done before we do any @@ -3781,6 +3853,37 @@ bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consens [&](const auto& header) { return CheckProofOfWork(header.GetHash(), header.nBits, consensusParams);}); } +bool IsBlockMutated(const CBlock& block, bool check_witness_root) +{ + BlockValidationState state; + if (!CheckMerkleRoot(block, state)) { + LogDebug(BCLog::VALIDATION, "Block mutated: %s\n", state.ToString()); + return true; + } + + if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) { + // Consider the block mutated if any transaction is 64 bytes in size (see 3.1 + // in "Weaknesses in Bitcoin’s Merkle Root Construction": + // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20190225/a27d8837/attachment-0001.pdf). + // + // Note: This is not a consensus change as this only applies to blocks that + // don't have a coinbase transaction and would therefore already be invalid. + return std::any_of(block.vtx.begin(), block.vtx.end(), + [](auto& tx) { return GetSerializeSize(TX_NO_WITNESS(tx)) == 64; }); + } else { + // Theoretically it is still possible for a block with a 64 byte + // coinbase transaction to be mutated but we neglect that possibility + // here as it requires at least 224 bits of work. + } + + if (!CheckWitnessMalleation(block, check_witness_root, state)) { + LogDebug(BCLog::VALIDATION, "Block mutated: %s\n", state.ToString()); + return true; + } + + return false; +} + arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers) { arith_uint256 total_work{0}; @@ -3889,33 +3992,8 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat // * There must be at least one output whose scriptPubKey is a single 36-byte push, the first 4 bytes of which are // {0xaa, 0x21, 0xa9, 0xed}, and the following 32 bytes are SHA256^2(witness root, witness reserved value). In case there are // multiple, the last one is used. - bool fHaveWitness = false; - if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT)) { - int commitpos = GetWitnessCommitmentIndex(block); - if (commitpos != NO_WITNESS_COMMITMENT) { - bool malleated = false; - uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated); - // The malleation check is ignored; as the transaction tree itself - // already does not permit it, it is impossible to trigger in the - // witness tree. - if (block.vtx[0]->vin[0].scriptWitness.stack.size() != 1 || block.vtx[0]->vin[0].scriptWitness.stack[0].size() != 32) { - return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-nonce-size", strprintf("%s : invalid witness reserved value size", __func__)); - } - CHash256().Write(hashWitness).Write(block.vtx[0]->vin[0].scriptWitness.stack[0]).Finalize(hashWitness); - if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) { - return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "bad-witness-merkle-match", strprintf("%s : witness merkle commitment mismatch", __func__)); - } - fHaveWitness = true; - } - } - - // No witness data is allowed in blocks that don't commit to witness data, as this would otherwise leave room for spam - if (!fHaveWitness) { - for (const auto& tx : block.vtx) { - if (tx->HasWitness()) { - return state.Invalid(BlockValidationResult::BLOCK_MUTATED, "unexpected-witness", strprintf("%s : unexpected witness data found", __func__)); - } - } + if (!CheckWitnessMalleation(block, DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_SEGWIT), state)) { + return false; } // After the coinbase witness reserved value and commitment are verified, diff --git a/src/validation.h b/src/validation.h index 94765bfbcd..aeef875e3f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -379,6 +379,9 @@ bool TestBlockValidity(BlockValidationState& state, /** Check with the proof of work on each blockheader matches the value in nBits */ bool HasValidProofOfWork(const std::vector<CBlockHeader>& headers, const Consensus::Params& consensusParams); +/** Check if a block has been mutated (with respect to its merkle root and witness commitments). */ +bool IsBlockMutated(const CBlock& block, bool check_witness_root); + /** Return the sum of the work on a given set of headers */ arith_uint256 CalculateHeadersWork(const std::vector<CBlockHeader>& headers); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 26c5256f6f..3ac09430d8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2607,8 +2607,10 @@ util::Result<CTxDestination> ReserveDestination::GetReservedDestination(bool int if (nIndex == -1) { CKeyPool keypool; - auto op_address = m_spk_man->GetReservedDestination(type, internal, nIndex, keypool); + int64_t index; + auto op_address = m_spk_man->GetReservedDestination(type, internal, index, keypool); if (!op_address) return op_address; + nIndex = index; address = *op_address; fInternal = keypool.fInternal; } |