kernel: Expose context-free block validation
What changed, and why it matters
This commit adds a new public API to Bitcoin Core's kernel library that lets outside callers run basic sanity checks on a raw Bitcoin block (for example, checking size limits, coinbase structure, and optionally proof-of-work and the merkle root). It is purely an addition of a new validation entry point and its tests. There is no bug fix, no change to existing consensus logic, and no indication of a security issue.
No security action required. This is a feature/API addition. Normal code review and CI testing are sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change exposes CheckBlock through the bitcoinkernel C API as btck_block_check and through the C++ wrapper as Block::Check(), with a new BlockCheckFlags bitmask controlling POW and merkle-root verification. It also adds unit tests covering a known valid mainnet block, mutated merkle root, invalid POW, invalid coinbase, and truncated deserialization. The implementation delegates directly to the existing CheckBlock routine without altering it.
Changed components
src/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cppInspect captured patch +130 / −0
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index b1180894..3295fe15 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -1129,6 +1129,19 @@ btck_Block* btck_block_copy(const btck_Block* block)
return btck_Block::copy(block);
}
+int btck_block_check(const btck_Block* block, const btck_ConsensusParams* consensus_params, btck_BlockCheckFlags flags, btck_BlockValidationState* validation_state)
+{
+ auto& state = btck_BlockValidationState::get(validation_state);
+ state = BlockValidationState{};
+
+ const bool check_pow = (flags & btck_BlockCheckFlags_POW) != 0;
+ const bool check_merkle = (flags & btck_BlockCheckFlags_MERKLE) != 0;
+
+ const bool result = CheckBlock(*btck_Block::get(block), state, btck_ConsensusParams::get(consensus_params), /*fCheckPOW=*/check_pow, /*fCheckMerkleRoot=*/check_merkle);
+
+ return result ? 1 : 0;
+}
+
size_t btck_block_count_transactions(const btck_Block* block)
{
return btck_Block::get(block)->vtx.size();
diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
index 971c5380..a7a861d4 100644
--- a/src/kernel/bitcoinkernel.h
+++ b/src/kernel/bitcoinkernel.h
@@ -1263,6 +1263,40 @@ BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_create
BITCOINKERNEL_API btck_Block* BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_copy(
const btck_Block* block) BITCOINKERNEL_ARG_NONNULL(1);
+/** Bitflags to control context-free block checks (optional). */
+typedef uint32_t btck_BlockCheckFlags;
+#define btck_BlockCheckFlags_BASE ((btck_BlockCheckFlags)0) //!< run the base context-free block checks only
+#define btck_BlockCheckFlags_POW ((btck_BlockCheckFlags)(1U << 0)) //!< run CheckProofOfWork via CheckBlockHeader
+#define btck_BlockCheckFlags_MERKLE ((btck_BlockCheckFlags)(1U << 1)) //!< verify merkle root (and mutation detection)
+#define btck_BlockCheckFlags_ALL ((btck_BlockCheckFlags)(btck_BlockCheckFlags_POW | btck_BlockCheckFlags_MERKLE)) //!< enable all optional context-free block checks
+
+/**
+ * @brief Perform context-free validation checks on a btck_Block.
+ *
+ * Runs the base context-free block checks (size limits, coinbase structure,
+ * transaction checks, and sigop limits) using the supplied
+ * btck_ConsensusParams. The proof-of-work and merkle-root checks are optional
+ * and can be toggled via @p flags. Note that this does not include any
+ * transaction script, timestamps, order, or other checks that may require more
+ * context.
+ *
+ * @param[in] block Non-null, btck_Block to validate.
+ * @param[in] consensus_params Non-null, btck_ConsensusParams for validation.
+ * @param[in] flags Bitmask of btck_BlockCheckFlags controlling the
+ * optional POW and merkle-root checks. Use
+ * btck_BlockCheckFlags_BASE to run only the base
+ * checks.
+ * @param[in,out] validation_state Non-null, previously created with
+ * btck_block_validation_state_create and updated
+ * in-place with the validation result.
+ * @return 1 if the btck_Block passed the checks, 0 otherwise.
+ */
+BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_block_check(
+ const btck_Block* block,
+ const btck_ConsensusParams* consensus_params,
+ btck_BlockCheckFlags flags,
+ btck_BlockValidationState* validation_state) BITCOINKERNEL_ARG_NONNULL(1, 2, 4);
+
/**
* @brief Count the number of transactions contained in a block.
*
diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
index 5f22fb8f..269bcdc1 100644
--- a/src/kernel/bitcoinkernel_wrapper.h
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -97,6 +97,13 @@ enum class ScriptVerificationFlags : btck_ScriptVerificationFlags {
ALL = btck_ScriptVerificationFlags_ALL
};
+enum class BlockCheckFlags : btck_BlockCheckFlags {
+ BASE = btck_BlockCheckFlags_BASE,
+ POW = btck_BlockCheckFlags_POW,
+ MERKLE = btck_BlockCheckFlags_MERKLE,
+ ALL = btck_BlockCheckFlags_ALL
+};
+
template <typename T>
struct is_bitmask_enum : std::false_type {
};
@@ -105,6 +112,10 @@ template <>
struct is_bitmask_enum<ScriptVerificationFlags> : std::true_type {
};
+template <>
+struct is_bitmask_enum<BlockCheckFlags> : std::true_type {
+};
+
template <typename T>
concept BitmaskEnum = is_bitmask_enum<T>::value;
@@ -370,6 +381,7 @@ public:
class PrecomputedTransactionData;
class Transaction;
class TransactionOutput;
+class BlockValidationState;
template <typename Derived>
class ScriptPubkeyApi
@@ -803,6 +815,10 @@ public:
return TransactionView{btck_block_get_transaction_at(get(), index)};
}
+ bool Check(const ConsensusParamsView& consensus_params,
+ BlockCheckFlags flags,
+ BlockValidationState& state) const;
+
MAKE_RANGE_METHOD(Transactions, Block, &Block::CountTransactions, &Block::GetTransaction, *this)
BlockHash GetHash() const
@@ -958,6 +974,13 @@ public:
BlockValidationState(const BlockValidationStateView& view) : Handle{view} {}
};
+inline bool Block::Check(const ConsensusParamsView& consensus_params,
+ BlockCheckFlags flags,
+ BlockValidationState& state) const
+{
+ return btck_block_check(get(), consensus_params.get(), static_cast<btck_BlockCheckFlags>(flags), state.get()) == 1;
+}
+
class ValidationInterface
{
public:
diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
index d4a8647b..5151f5b3 100644
--- a/src/test/kernel/test_kernel.cpp
+++ b/src/test/kernel/test_kernel.cpp
@@ -10,6 +10,7 @@
#include <boost/test/included/unit_test.hpp>
#include <test/kernel/block_data.h>
+#include <test/util/common.h>
#include <charconv>
#include <cstdint>
@@ -913,6 +914,65 @@ void chainman_mainnet_validation_test(TestDirectory& test_directory)
BOOST_CHECK(!new_block);
}
+BOOST_AUTO_TEST_CASE(btck_check_block_context_free)
+{
+ constexpr size_t MERKLE_ROOT_OFFSET{4 + 32};
+ constexpr size_t NBITS_OFFSET{4 + 32 + 32 + 4};
+ constexpr size_t COINBASE_PREVOUT_N_OFFSET{4 + 32 + 32 + 4 + 4 + 4 + 1 + 4 + 1 + 32};
+
+ // Mainnet block 1
+ auto raw_block = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000");
+
+ // Context-free block checks still need consensus params for the optional
+ // proof-of-work validation path.
+ ChainParams mainnet_params{ChainType::MAINNET};
+ auto consensus_params = mainnet_params.GetConsensusParams();
+
+ Block block{raw_block};
+ BlockValidationState state;
+
+ BOOST_CHECK(block.Check(consensus_params, BlockCheckFlags::BASE, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID);
+
+ BOOST_CHECK(block.Check(consensus_params, BlockCheckFlags::ALL, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID);
+
+ auto bad_merkle_block_data = raw_block;
+ bad_merkle_block_data[MERKLE_ROOT_OFFSET] ^= std::byte{0x01};
+ Block bad_merkle_block{bad_merkle_block_data};
+
+ BOOST_CHECK(!bad_merkle_block.Check(consensus_params, BlockCheckFlags::MERKLE, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID);
+ BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::MUTATED);
+
+ BOOST_CHECK(bad_merkle_block.Check(consensus_params, BlockCheckFlags::BASE, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID);
+
+ auto bad_pow_block_data = raw_block;
+ bad_pow_block_data[NBITS_OFFSET + 3] = std::byte{0x1c};
+ Block bad_pow_block{bad_pow_block_data};
+
+ BOOST_CHECK(!bad_pow_block.Check(consensus_params, BlockCheckFlags::POW, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID);
+ BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::INVALID_HEADER);
+
+ BOOST_CHECK(bad_pow_block.Check(consensus_params, BlockCheckFlags::MERKLE, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::VALID);
+
+ auto bad_base_block_data = raw_block;
+ bad_base_block_data[COINBASE_PREVOUT_N_OFFSET] = std::byte{0x00};
+ Block bad_base_block{bad_base_block_data};
+
+ BOOST_CHECK(!bad_base_block.Check(consensus_params, BlockCheckFlags::BASE, state));
+ BOOST_CHECK(state.GetValidationMode() == ValidationMode::INVALID);
+ BOOST_CHECK(state.GetBlockValidationResult() == BlockValidationResult::CONSENSUS);
+
+ // Test with invalid truncated block data.
+ auto truncated_block_data = hex_string_to_byte_vec("010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299");
+ BOOST_CHECK_EXCEPTION(Block{truncated_block_data}, std::runtime_error,
+ HasReason{"failed to instantiate btck object"});
+}
+
BOOST_AUTO_TEST_CASE(btck_chainman_mainnet_tests)
{
auto test_directory{TestDirectory{"mainnet_test_bitcoin_kernel"}};
Why this scored 14/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.