aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am2
-rw-r--r--src/crc32c/src/crc32c_arm64.cc32
-rw-r--r--src/crypto/sha256.cpp8
-rw-r--r--src/net_processing.cpp11
-rw-r--r--src/primitives/block.h8
-rw-r--r--src/serialize.h20
-rw-r--r--src/test/fuzz/net.cpp37
-rw-r--r--src/test/streams_tests.cpp9
-rw-r--r--src/test/system_tests.cpp32
-rw-r--r--src/test/validation_tests.cpp215
-rw-r--r--src/util/transaction_identifier.h1
-rw-r--r--src/validation.cpp154
-rw-r--r--src/validation.h3
-rw-r--r--src/wallet/wallet.cpp4
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;
}