kernel: Introduce initial kernel C header API
What changed, and why it matters
This commit introduces a brand-new C and C++ programming interface for Bitcoin Core's experimental "kernel" library. It exposes functions for parsing transactions, reading transaction outputs, and verifying Bitcoin scripts. The change is primarily a feature addition: it replaces the older libbitcoinconsensus-style API with a cleaner, modern one and adds tests. The commit message notes that one new error code was added specifically to prevent crashes when callers combine script-verification flags in invalid ways. There is no direct evidence in the commit of an exploitable vulnerability in existing code, but the new API does handle untrusted transaction data and must be used carefully.
Treat this as a new attack surface rather than an active vulnerability. Reviewers should audit the C API boundary for lifetime safety (especially the const-pointer "views"), ensure exceptions cannot leak across the C boundary, verify that all flag combinations are correctly rejected, and run the new test_kernel binary in CI. Downstream users of the experimental library should pin to a release that has undergone further review before relying on it for consensus validation in production.
Security signals we found
New public C/C++ API exposes consensus-critical parsing and script verification to external callers
Commit message explicitly states a new error code was added because invalid flag combinations would otherwise cause a crash
is_valid_flag_combination() rejects CLEANSTACK without P2SH/WITNESS and WITNESS without P2SH
TAPROOT verification requires spent_outputs to be provided; missing them returns an error status rather than dereferencing null
Uses reinterpret_cast between C opaque structs and C++ objects, requiring strict lifetime discipline
C++ wrapper stores raw C pointers in View/Handle classes; misuse can produce use-after-free (acknowledged in a commented-out test)
WriterStream callback can throw across the C API boundary if the user's write callback returns non-zero
No input validation beyond deserialization exceptions for raw transaction/script data
Evidence from the diff
The patch adds a public C header (bitcoinkernel.h), a C++ wrapper (bitcoinkernel_wrapper.h), an implementation file (bitcoinkernel.cpp), and a new test binary (test_kernel.cpp). The C API exposes opaque handles for transactions, script pubkeys, and transaction outputs, plus a script-verification entry point (btck_script_pubkey_verify). Internally it reuses Bitcoin Core’s consensus code (CTransaction, CTxOut, CScript, VerifyScript, PrecomputedTransactionData). A static kernel::Context is used for context-free operations. The implementation adds an is_valid_flag_combination() guard and a new status code (btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION) to reject dangerous flag combinations that “would otherwise cause a crash.” Build and CI files are updated to compile and run the new test. No CVE, advisory, or researcher attribution is present in the supplied materials.
Changed components
src/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cppsrc/kernel/CMakeLists.txtsrc/test/kernel/CMakeLists.txtsrc/CMakeLists.txtCMakeLists.txtCI build scripts and workflow filesInspect captured patch +1415 / −9
diff --git a/.github/ci-test-each-commit-exec.py b/.github/ci-test-each-commit-exec.py
index 3b2eaeeb..959a1579 100755
--- a/.github/ci-test-each-commit-exec.py
+++ b/.github/ci-test-each-commit-exec.py
@@ -42,6 +42,8 @@ def main():
"-DBUILD_BENCH=ON",
"-DBUILD_FUZZ_BINARY=ON",
"-DWITH_USDT=ON",
+ "-DBUILD_KERNEL_LIB=ON",
+ "-DBUILD_KERNEL_TEST=ON",
"-DCMAKE_CXX_FLAGS=-Wno-error=unused-member-function",
])
run(["cmake", "--build", "build", "-j", str(num_procs)])
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a05e8f96..8370afd8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -202,7 +202,7 @@ jobs:
job-type: [standard, fuzz]
include:
- job-type: standard
- generate-options: '-DBUILD_GUI=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DWERROR=ON'
+ generate-options: '-DBUILD_GUI=ON -DWITH_ZMQ=ON -DBUILD_BENCH=ON -DBUILD_KERNEL_LIB=ON -DWERROR=ON'
job-name: 'Windows native, VS 2022'
- job-type: fuzz
generate-options: '-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON -DVCPKG_MANIFEST_FEATURES="wallet" -DBUILD_GUI=OFF -DBUILD_FOR_FUZZING=ON -DWERROR=ON'
@@ -280,7 +280,7 @@ jobs:
$exeName = $_.Name
# Skip as they currently do not have manifests
- if ($exeName -eq "fuzz.exe" -or $exeName -eq "bench_bitcoin.exe" -or $exeName -eq "test_bitcoin-qt.exe") {
+ if ($exeName -eq "fuzz.exe" -or $exeName -eq "bench_bitcoin.exe" -or $exeName -eq "test_bitcoin-qt.exe" -or $exeName -eq "test_kernel.exe") {
Write-Host "Skipping $exeName (no manifest present)"
return
}
@@ -399,7 +399,7 @@ jobs:
$exeName = $_.Name
# Skip as they currently do not have manifests
- if ($exeName -eq "fuzz.exe" -or $exeName -eq "bench_bitcoin.exe") {
+ if ($exeName -eq "fuzz.exe" -or $exeName -eq "bench_bitcoin.exe" -or $exeName -eq "test_kernel.exe") {
Write-Host "Skipping $exeName (no manifest present)"
return
}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c50e2f6d..3749cf7c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -105,6 +105,7 @@ option(BUILD_UTIL "Build bitcoin-util executable." ${BUILD_TESTS})
option(BUILD_UTIL_CHAINSTATE "Build experimental bitcoin-chainstate executable." OFF)
option(BUILD_KERNEL_LIB "Build experimental bitcoinkernel library." ${BUILD_UTIL_CHAINSTATE})
+option(BUILD_KERNEL_TEST "Build tests for the experimental bitcoinkernel library." ${BUILD_KERNEL_LIB})
option(ENABLE_WALLET "Enable wallet." ON)
if(ENABLE_WALLET)
@@ -210,6 +211,7 @@ if(BUILD_FOR_FUZZING)
set(BUILD_UTIL OFF)
set(BUILD_UTIL_CHAINSTATE OFF)
set(BUILD_KERNEL_LIB OFF)
+ set(BUILD_KERNEL_TEST OFF)
set(BUILD_WALLET_TOOL OFF)
set(BUILD_GUI OFF)
set(ENABLE_EXTERNAL_SIGNER OFF)
@@ -667,6 +669,7 @@ message(" bitcoin-util ........................ ${BUILD_UTIL}")
message(" bitcoin-wallet ...................... ${BUILD_WALLET_TOOL}")
message(" bitcoin-chainstate (experimental) ... ${BUILD_UTIL_CHAINSTATE}")
message(" libbitcoinkernel (experimental) ..... ${BUILD_KERNEL_LIB}")
+message(" kernel-test (experimental) .......... ${BUILD_KERNEL_TEST}")
message("Optional features:")
message(" wallet support ...................... ${ENABLE_WALLET}")
message(" external signer ..................... ${ENABLE_EXTERNAL_SIGNER}")
diff --git a/ci/test/00_setup_env_mac_cross.sh b/ci/test/00_setup_env_mac_cross.sh
index 027741da..c7de0334 100755
--- a/ci/test/00_setup_env_mac_cross.sh
+++ b/ci/test/00_setup_env_mac_cross.sh
@@ -17,4 +17,4 @@ export XCODE_BUILD_ID=15A240d
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
-export BITCOIN_CONFIG="-DBUILD_GUI=ON -DREDUCE_EXPORTS=ON"
+export BITCOIN_CONFIG="-DBUILD_GUI=ON -DBUILD_KERNEL_LIB=ON -DREDUCE_EXPORTS=ON"
diff --git a/ci/test/00_setup_env_mac_native.sh b/ci/test/00_setup_env_mac_native.sh
index 41a3bc45..00770beb 100755
--- a/ci/test/00_setup_env_mac_native.sh
+++ b/ci/test/00_setup_env_mac_native.sh
@@ -12,7 +12,7 @@ export CONTAINER_NAME="ci_mac_native" # macos does not use a container, but the
export PIP_PACKAGES="--break-system-packages zmq"
export GOAL="install deploy"
export CMAKE_GENERATOR="Ninja"
-export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DREDUCE_EXPORTS=ON -DCMAKE_EXE_LINKER_FLAGS='-Wl,-stack_size -Wl,0x80000'"
+export BITCOIN_CONFIG="-DBUILD_GUI=ON -DWITH_ZMQ=ON -DBUILD_KERNEL_LIB=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_TEST=ON -DREDUCE_EXPORTS=ON -DCMAKE_EXE_LINKER_FLAGS='-Wl,-stack_size -Wl,0x80000'"
export CI_OS_NAME="macos"
export NO_DEPENDS=1
export OSX_SDK=""
diff --git a/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh
index 58561e55..b1e0c65e 100755
--- a/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh
+++ b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh
@@ -13,4 +13,4 @@ export PACKAGES="python3-zmq python3-pip clang-16 llvm-16 libc++abi-16-dev libc+
export PIP_PACKAGES="--break-system-packages pycapnp"
export DEP_OPTS="NO_WALLET=1 CC=clang-16 CXX='clang++-16 -stdlib=libc++'"
export GOAL="install"
-export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_LIB=ON -DBUILD_SHARED_LIBS=ON"
+export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_UTIL_CHAINSTATE=ON -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON -DBUILD_SHARED_LIBS=ON"
diff --git a/ci/test/00_setup_env_win64.sh b/ci/test/00_setup_env_win64.sh
index f929f34c..1ea67230 100755
--- a/ci/test/00_setup_env_win64.sh
+++ b/ci/test/00_setup_env_win64.sh
@@ -13,5 +13,5 @@ export PACKAGES="g++-mingw-w64-x86-64-posix nsis"
export RUN_UNIT_TESTS=false
export RUN_FUNCTIONAL_TESTS=false
export GOAL="deploy"
-export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_GUI_TESTS=OFF \
+export BITCOIN_CONFIG="-DREDUCE_EXPORTS=ON -DBUILD_GUI_TESTS=OFF -DBUILD_KERNEL_LIB=ON -DBUILD_KERNEL_TEST=ON \
-DCMAKE_CXX_FLAGS='-Wno-error=maybe-uninitialized'"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bcadd675..7c7e0fe7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -404,6 +404,9 @@ endif()
if(BUILD_KERNEL_LIB)
add_subdirectory(kernel)
+ if (BUILD_KERNEL_TEST)
+ add_subdirectory(test/kernel)
+ endif()
endif()
if(BUILD_UTIL_CHAINSTATE)
diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt
index c45ce781..7b2f3c7d 100644
--- a/src/kernel/CMakeLists.txt
+++ b/src/kernel/CMakeLists.txt
@@ -110,6 +110,11 @@ set_target_properties(bitcoinkernel PROPERTIES
add_custom_target(libbitcoinkernel)
add_dependencies(libbitcoinkernel bitcoinkernel)
+get_target_property(bitcoinkernel_type bitcoinkernel TYPE)
+if(bitcoinkernel_type STREQUAL "STATIC_LIBRARY")
+ target_compile_definitions(bitcoinkernel PUBLIC BITCOINKERNEL_STATIC)
+endif()
+
configure_file(${PROJECT_SOURCE_DIR}/libbitcoinkernel.pc.in ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc @ONLY)
install(FILES ${PROJECT_BINARY_DIR}/libbitcoinkernel.pc DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" COMPONENT libbitcoinkernel)
@@ -124,3 +129,5 @@ install(TARGETS bitcoinkernel
DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT libbitcoinkernel
)
+
+install(FILES bitcoinkernel.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT libbitcoinkernel)
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 9096b6f7..50608dc3 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -1,11 +1,255 @@
-// Copyright (c) 2022 The Bitcoin Core developers
+// Copyright (c) 2022-present The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#define BITCOINKERNEL_BUILD
+
+#include <kernel/bitcoinkernel.h>
+
+#include <consensus/amount.h>
+#include <kernel/context.h>
+#include <primitives/transaction.h>
+#include <script/interpreter.h>
+#include <script/script.h>
+#include <serialize.h>
+#include <streams.h>
#include <util/translation.h>
+#include <cstddef>
+#include <cstring>
+#include <exception>
#include <functional>
+#include <span>
#include <string>
+#include <utility>
+#include <vector>
// Define G_TRANSLATION_FUN symbol in libbitcoinkernel library so users of the
// library aren't required to export this symbol
-extern const TranslateFn G_TRANSLATION_FUN{nullptr};
+extern const std::function<std::string(const char*)> G_TRANSLATION_FUN{nullptr};
+
+static const kernel::Context btck_context_static{};
+
+namespace {
+
+bool is_valid_flag_combination(script_verify_flags flags)
+{
+ if (flags & SCRIPT_VERIFY_CLEANSTACK && ~flags & (SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS)) return false;
+ if (flags & SCRIPT_VERIFY_WITNESS && ~flags & SCRIPT_VERIFY_P2SH) return false;
+ return true;
+}
+
+class WriterStream
+{
+private:
+ btck_WriteBytes m_writer;
+ void* m_user_data;
+
+public:
+ WriterStream(btck_WriteBytes writer, void* user_data)
+ : m_writer{writer}, m_user_data{user_data} {}
+
+ //
+ // Stream subset
+ //
+ void write(std::span<const std::byte> src)
+ {
+ if (m_writer(std::data(src), src.size(), m_user_data) != 0) {
+ throw std::runtime_error("Failed to write serialization data");
+ }
+ }
+
+ template <typename T>
+ WriterStream& operator<<(const T& obj)
+ {
+ ::Serialize(*this, obj);
+ return *this;
+ }
+};
+
+template <typename C, typename CPP>
+struct Handle {
+ static C* ref(CPP* cpp_type)
+ {
+ return reinterpret_cast<C*>(cpp_type);
+ }
+
+ static const C* ref(const CPP* cpp_type)
+ {
+ return reinterpret_cast<const C*>(cpp_type);
+ }
+
+ template <typename... Args>
+ static C* create(Args&&... args)
+ {
+ auto cpp_obj{std::make_unique<CPP>(std::forward<Args>(args)...)};
+ return reinterpret_cast<C*>(cpp_obj.release());
+ }
+
+ static C* copy(const C* ptr)
+ {
+ auto cpp_obj{std::make_unique<CPP>(get(ptr))};
+ return reinterpret_cast<C*>(cpp_obj.release());
+ }
+
+ static const CPP& get(const C* ptr)
+ {
+ return *reinterpret_cast<const CPP*>(ptr);
+ }
+
+ static void operator delete(void* ptr)
+ {
+ delete reinterpret_cast<CPP*>(ptr);
+ }
+};
+
+} // namespace
+
+struct btck_Transaction : Handle<btck_Transaction, std::shared_ptr<const CTransaction>> {};
+struct btck_TransactionOutput : Handle<btck_TransactionOutput, CTxOut> {};
+struct btck_ScriptPubkey : Handle<btck_ScriptPubkey, CScript> {};
+
+btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
+{
+ try {
+ DataStream stream{std::span{reinterpret_cast<const std::byte*>(raw_transaction), raw_transaction_len}};
+ return btck_Transaction::create(std::make_shared<const CTransaction>(deserialize, TX_WITH_WITNESS, stream));
+ } catch (...) {
+ return nullptr;
+ }
+}
+
+size_t btck_transaction_count_outputs(const btck_Transaction* transaction)
+{
+ return btck_Transaction::get(transaction)->vout.size();
+}
+
+const btck_TransactionOutput* btck_transaction_get_output_at(const btck_Transaction* transaction, size_t output_index)
+{
+ const CTransaction& tx = *btck_Transaction::get(transaction);
+ assert(output_index < tx.vout.size());
+ return btck_TransactionOutput::ref(&tx.vout[output_index]);
+}
+
+size_t btck_transaction_count_inputs(const btck_Transaction* transaction)
+{
+ return btck_Transaction::get(transaction)->vin.size();
+}
+
+btck_Transaction* btck_transaction_copy(const btck_Transaction* transaction)
+{
+ return btck_Transaction::copy(transaction);
+}
+
+int btck_transaction_to_bytes(const btck_Transaction* transaction, btck_WriteBytes writer, void* user_data)
+{
+ try {
+ WriterStream ws{writer, user_data};
+ ws << TX_WITH_WITNESS(btck_Transaction::get(transaction));
+ return 0;
+ } catch (...) {
+ return -1;
+ }
+}
+
+void btck_transaction_destroy(btck_Transaction* transaction)
+{
+ delete transaction;
+}
+
+btck_ScriptPubkey* btck_script_pubkey_create(const void* script_pubkey, size_t script_pubkey_len)
+{
+ auto data = std::span{reinterpret_cast<const uint8_t*>(script_pubkey), script_pubkey_len};
+ return btck_ScriptPubkey::create(data.begin(), data.end());
+}
+
+int btck_script_pubkey_to_bytes(const btck_ScriptPubkey* script_pubkey_, btck_WriteBytes writer, void* user_data)
+{
+ const auto& script_pubkey{btck_ScriptPubkey::get(script_pubkey_)};
+ return writer(script_pubkey.data(), script_pubkey.size(), user_data);
+}
+
+btck_ScriptPubkey* btck_script_pubkey_copy(const btck_ScriptPubkey* script_pubkey)
+{
+ return btck_ScriptPubkey::copy(script_pubkey);
+}
+
+void btck_script_pubkey_destroy(btck_ScriptPubkey* script_pubkey)
+{
+ delete script_pubkey;
+}
+
+btck_TransactionOutput* btck_transaction_output_create(const btck_ScriptPubkey* script_pubkey, int64_t amount)
+{
+ return btck_TransactionOutput::create(amount, btck_ScriptPubkey::get(script_pubkey));
+}
+
+btck_TransactionOutput* btck_transaction_output_copy(const btck_TransactionOutput* output)
+{
+ return btck_TransactionOutput::copy(output);
+}
+
+const btck_ScriptPubkey* btck_transaction_output_get_script_pubkey(const btck_TransactionOutput* output)
+{
+ return btck_ScriptPubkey::ref(&btck_TransactionOutput::get(output).scriptPubKey);
+}
+
+int64_t btck_transaction_output_get_amount(const btck_TransactionOutput* output)
+{
+ return btck_TransactionOutput::get(output).nValue;
+}
+
+void btck_transaction_output_destroy(btck_TransactionOutput* output)
+{
+ delete output;
+}
+
+int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,
+ const int64_t amount,
+ const btck_Transaction* tx_to,
+ const btck_TransactionOutput** spent_outputs_, size_t spent_outputs_len,
+ const unsigned int input_index,
+ const btck_ScriptVerificationFlags flags,
+ btck_ScriptVerifyStatus* status)
+{
+ // Assert that all specified flags are part of the interface before continuing
+ assert((flags & ~btck_ScriptVerificationFlags_ALL) == 0);
+
+ if (!is_valid_flag_combination(script_verify_flags::from_int(flags))) {
+ if (status) *status = btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION;
+ return 0;
+ }
+
+ if (flags & btck_ScriptVerificationFlags_TAPROOT && spent_outputs_ == nullptr) {
+ if (status) *status = btck_ScriptVerifyStatus_ERROR_SPENT_OUTPUTS_REQUIRED;
+ return 0;
+ }
+
+ if (status) *status = btck_ScriptVerifyStatus_OK;
+
+ const CTransaction& tx{*btck_Transaction::get(tx_to)};
+ std::vector<CTxOut> spent_outputs;
+ if (spent_outputs_ != nullptr) {
+ assert(spent_outputs_len == tx.vin.size());
+ spent_outputs.reserve(spent_outputs_len);
+ for (size_t i = 0; i < spent_outputs_len; i++) {
+ const CTxOut& tx_out{btck_TransactionOutput::get(spent_outputs_[i])};
+ spent_outputs.push_back(tx_out);
+ }
+ }
+
+ assert(input_index < tx.vin.size());
+ PrecomputedTransactionData txdata{tx};
+
+ if (spent_outputs_ != nullptr && flags & btck_ScriptVerificationFlags_TAPROOT) {
+ txdata.Init(tx, std::move(spent_outputs));
+ }
+
+ bool result = VerifyScript(tx.vin[input_index].scriptSig,
+ btck_ScriptPubkey::get(script_pubkey),
+ &tx.vin[input_index].scriptWitness,
+ script_verify_flags::from_int(flags),
+ TransactionSignatureChecker(&tx, input_index, amount, txdata, MissingDataBehavior::FAIL),
+ nullptr);
+ return result ? 1 : 0;
+}
diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
new file mode 100644
index 00000000..bb16cd11
--- /dev/null
+++ b/src/kernel/bitcoinkernel.h
@@ -0,0 +1,340 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_KERNEL_BITCOINKERNEL_H
+#define BITCOIN_KERNEL_BITCOINKERNEL_H
+
+#ifndef __cplusplus
+#include <stddef.h>
+#include <stdint.h>
+#else
+#include <cstddef>
+#include <cstdint>
+#endif // __cplusplus
+
+#ifndef BITCOINKERNEL_API
+ #ifdef BITCOINKERNEL_BUILD
+ #if defined(_WIN32)
+ #define BITCOINKERNEL_API __declspec(dllexport)
+ #else
+ #define BITCOINKERNEL_API __attribute__((visibility("default")))
+ #endif
+ #else
+ #if defined(_WIN32) && !defined(BITCOINKERNEL_STATIC)
+ #define BITCOINKERNEL_API __declspec(dllimport)
+ #else
+ #define BITCOINKERNEL_API
+ #endif
+ #endif
+#endif
+
+/* Warning attributes */
+#if defined(__GNUC__)
+ #define BITCOINKERNEL_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+#else
+ #define BITCOINKERNEL_WARN_UNUSED_RESULT
+#endif
+#if !defined(BITCOINKERNEL_BUILD) && defined(__GNUC__)
+ #define BITCOINKERNEL_ARG_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
+#else
+ #define BITCOINKERNEL_ARG_NONNULL(...)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * @page remarks Remarks
+ *
+ * @section context Context
+ *
+ * The library provides a built-in static constant kernel context. This static
+ * context offers only limited functionality. It detects and self-checks the
+ * correct sha256 implementation, initializes the random number generator and
+ * self-checks the secp256k1 static context. It is used internally for
+ * otherwise "context-free" operations. This means that the user is not
+ * required to initialize their own context before using the library.
+ *
+ * @section error Error handling
+ *
+ * Functions communicate an error through their return types, usually returning
+ * a nullptr, 0, or false if an error is encountered. Additionally, verification
+ * functions, e.g. for scripts, may communicate more detailed error information
+ * through status code out parameters.
+ *
+ * @section pointer Pointer and argument conventions
+ *
+ * The user is responsible for de-allocating the memory owned by pointers
+ * returned by functions. Typically pointers returned by *_create(...) functions
+ * can be de-allocated by corresponding *_destroy(...) functions.
+ *
+ * A function that takes pointer arguments makes no assumptions on their
+ * lifetime. Once the function returns the user can safely de-allocate the
+ * passed in arguments.
+ *
+ * Const pointers represent views, and do not transfer ownership. Lifetime
+ * guarantees of these objects are described in the respective documentation.
+ * Ownership of these resources may be taken by copying. They are typically
+ * used for iteration with minimal overhead and require some care by the
+ * programmer that their lifetime is not extended beyond that of the original
+ * object.
+ *
+ * Array lengths follow the pointer argument they describe.
+ */
+
+/**
+ * Opaque data structure for holding a transaction.
+ */
+typedef struct btck_Transaction btck_Transaction;
+
+/**
+ * Opaque data structure for holding a script pubkey.
+ */
+typedef struct btck_ScriptPubkey btck_ScriptPubkey;
+
+/**
+ * Opaque data structure for holding a transaction output.
+ */
+typedef struct btck_TransactionOutput btck_TransactionOutput;
+
+/**
+ * A collection of status codes that may be issued by the script verify function.
+ */
+typedef uint8_t btck_ScriptVerifyStatus;
+#define btck_ScriptVerifyStatus_OK ((btck_ScriptVerifyStatus)(0))
+#define btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION ((btck_ScriptVerifyStatus)(1)) //!< The flags were combined in an invalid way.
+#define btck_ScriptVerifyStatus_ERROR_SPENT_OUTPUTS_REQUIRED ((btck_ScriptVerifyStatus)(2)) //!< The taproot flag was set, so valid spent_outputs have to be provided.
+
+/**
+ * Script verification flags that may be composed with each other.
+ */
+typedef uint32_t btck_ScriptVerificationFlags;
+#define btck_ScriptVerificationFlags_NONE ((btck_ScriptVerificationFlags)(0))
+#define btck_ScriptVerificationFlags_P2SH ((btck_ScriptVerificationFlags)(1U << 0)) //!< evaluate P2SH (BIP16) subscripts
+#define btck_ScriptVerificationFlags_DERSIG ((btck_ScriptVerificationFlags)(1U << 2)) //!< enforce strict DER (BIP66) compliance
+#define btck_ScriptVerificationFlags_NULLDUMMY ((btck_ScriptVerificationFlags)(1U << 4)) //!< enforce NULLDUMMY (BIP147)
+#define btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY ((btck_ScriptVerificationFlags)(1U << 9)) //!< enable CHECKLOCKTIMEVERIFY (BIP65)
+#define btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY ((btck_ScriptVerificationFlags)(1U << 10)) //!< enable CHECKSEQUENCEVERIFY (BIP112)
+#define btck_ScriptVerificationFlags_WITNESS ((btck_ScriptVerificationFlags)(1U << 11)) //!< enable WITNESS (BIP141)
+#define btck_ScriptVerificationFlags_TAPROOT ((btck_ScriptVerificationFlags)(1U << 17)) //!< enable TAPROOT (BIPs 341 & 342)
+#define btck_ScriptVerificationFlags_ALL ((btck_ScriptVerificationFlags)(btck_ScriptVerificationFlags_P2SH | \
+ btck_ScriptVerificationFlags_DERSIG | \
+ btck_ScriptVerificationFlags_NULLDUMMY | \
+ btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY | \
+ btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY | \
+ btck_ScriptVerificationFlags_WITNESS | \
+ btck_ScriptVerificationFlags_TAPROOT))
+
+/**
+ * Function signature for serializing data.
+ */
+typedef int (*btck_WriteBytes)(const void* bytes, size_t size, void* userdata);
+
+/** @name Transaction
+ * Functions for working with transactions.
+ */
+///@{
+
+/**
+ * @brief Create a new transaction from the serialized data.
+ *
+ * @param[in] raw_transaction Non-null.
+ * @param[in] raw_transaction_len Length of the serialized transaction.
+ * @return The transaction, or null on error.
+ */
+BITCOINKERNEL_API btck_Transaction* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_create(
+ const void* raw_transaction, size_t raw_transaction_len) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Copy a transaction. Transactions are reference counted, so this just
+ * increments the reference count.
+ *
+ * @param[in] transaction Non-null.
+ * @return The copied transaction.
+ */
+BITCOINKERNEL_API btck_Transaction* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_copy(
+ const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Serializes the transaction through the passed in callback to bytes.
+ * This is consensus serialization that is also used for the P2P network.
+ *
+ * @param[in] transaction Non-null.
+ * @param[in] writer Non-null, callback to a write bytes function.
+ * @param[in] user_data Holds a user-defined opaque structure that will be
+ * passed back through the writer callback.
+ * @return 0 on success.
+ */
+BITCOINKERNEL_API int btck_transaction_to_bytes(
+ const btck_Transaction* transaction,
+ btck_WriteBytes writer,
+ void* user_data) BITCOINKERNEL_ARG_NONNULL(1, 2);
+
+/**
+ * @brief Get the number of outputs of a transaction.
+ *
+ * @param[in] transaction Non-null.
+ * @return The number of outputs.
+ */
+BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count_outputs(
+ const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Get the transaction outputs at the provided index. The returned
+ * transaction output is not owned and depends on the lifetime of the
+ * transaction.
+ *
+ * @param[in] transaction Non-null.
+ * @param[in] output_index The index of the transaction output to be retrieved.
+ * @return The transaction output
+ */
+BITCOINKERNEL_API const btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_get_output_at(
+ const btck_Transaction* transaction, size_t output_index) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Get the number of inputs of a transaction.
+ *
+ * @param[in] transaction Non-null.
+ * @return The number of inputs.
+ */
+BITCOINKERNEL_API size_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_count_inputs(
+ const btck_Transaction* transaction) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * Destroy the transaction.
+ */
+BITCOINKERNEL_API void btck_transaction_destroy(btck_Transaction* transaction);
+
+///@}
+
+/** @name ScriptPubkey
+ * Functions for working with script pubkeys.
+ */
+///@{
+
+/**
+ * @brief Create a script pubkey from serialized data.
+ * @param[in] script_pubkey Non-null.
+ * @param[in] script_pubkey_len Length of the script pubkey data.
+ * @return The script pubkey.
+ */
+BITCOINKERNEL_API btck_ScriptPubkey* BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_create(
+ const void* script_pubkey, size_t script_pubkey_len) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Copy a script pubkey.
+ *
+ * @param[in] script_pubkey Non-null.
+ * @return The copied script pubkey.
+ */
+BITCOINKERNEL_API btck_ScriptPubkey* BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_copy(
+ const btck_ScriptPubkey* script_pubkey) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Verify if the input at input_index of tx_to spends the script pubkey
+ * under the constraints specified by flags. If the
+ * `btck_ScriptVerificationFlags_WITNESS` flag is set in the flags bitfield, the
+ * amount parameter is used. If the taproot flag is set, the spent outputs
+ * parameter is used to validate taproot transactions.
+ *
+ * @param[in] script_pubkey Non-null, script pubkey to be spent.
+ * @param[in] amount Amount of the script pubkey's associated output. May be zero if
+ * the witness flag is not set.
+ * @param[in] tx_to Non-null, transaction spending the script_pubkey.
+ * @param[in] spent_outputs Nullable if the taproot flag is not set. Points to an array of
+ * outputs spent by the transaction.
+ * @param[in] spent_outputs_len Length of the spent_outputs array.
+ * @param[in] input_index Index of the input in tx_to spending the script_pubkey.
+ * @param[in] flags Bitfield of btck_ScriptVerificationFlags controlling validation constraints.
+ * @param[out] status Nullable, will be set to an error code if the operation fails, or OK otherwise.
+ * @return 1 if the script is valid, 0 otherwise.
+ */
+BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_verify(
+ const btck_ScriptPubkey* script_pubkey,
+ int64_t amount,
+ const btck_Transaction* tx_to,
+ const btck_TransactionOutput** spent_outputs, size_t spent_outputs_len,
+ unsigned int input_index,
+ unsigned int flags,
+ btck_ScriptVerifyStatus* status) BITCOINKERNEL_ARG_NONNULL(1, 3);
+
+/**
+ * @brief Serializes the script pubkey through the passed in callback to bytes.
+ *
+ * @param[in] script_pubkey Non-null.
+ * @param[in] writer Non-null, callback to a write bytes function.
+ * @param[in] user_data Holds a user-defined opaque structure that will be
+ * passed back through the writer callback.
+ * @return 0 on success.
+ */
+BITCOINKERNEL_API int btck_script_pubkey_to_bytes(
+ const btck_ScriptPubkey* script_pubkey,
+ btck_WriteBytes writer,
+ void* user_data) BITCOINKERNEL_ARG_NONNULL(1, 2);
+
+/**
+ * Destroy the script pubkey.
+ */
+BITCOINKERNEL_API void btck_script_pubkey_destroy(btck_ScriptPubkey* script_pubkey);
+
+///@}
+
+/** @name TransactionOutput
+ * Functions for working with transaction outputs.
+ */
+///@{
+
+/**
+ * @brief Create a transaction output from a script pubkey and an amount.
+ *
+ * @param[in] script_pubkey Non-null.
+ * @param[in] amount The amount associated with the script pubkey for this output.
+ * @return The transaction output.
+ */
+BITCOINKERNEL_API btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_output_create(
+ const btck_ScriptPubkey* script_pubkey,
+ int64_t amount) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Get the script pubkey of the output. The returned
+ * script pubkey is not owned and depends on the lifetime of the
+ * transaction output.
+ *
+ * @param[in] transaction_output Non-null.
+ * @return The script pubkey.
+ */
+BITCOINKERNEL_API const btck_ScriptPubkey* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_output_get_script_pubkey(
+ const btck_TransactionOutput* transaction_output) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Get the amount in the output.
+ *
+ * @param[in] transaction_output Non-null.
+ * @return The amount.
+ */
+BITCOINKERNEL_API int64_t BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_output_get_amount(
+ const btck_TransactionOutput* transaction_output) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Copy a transaction output.
+ *
+ * @param[in] transaction_output Non-null.
+ * @return The copied transaction output.
+ */
+BITCOINKERNEL_API btck_TransactionOutput* BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_output_copy(
+ const btck_TransactionOutput* transaction_output) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * Destroy the transaction output.
+ */
+BITCOINKERNEL_API void btck_transaction_output_destroy(btck_TransactionOutput* transaction_output);
+
+///@}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
+
+#endif // BITCOIN_KERNEL_BITCOINKERNEL_H
diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
new file mode 100644
index 00000000..d50dee38
--- /dev/null
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -0,0 +1,445 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
+#define BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
+
+#include <kernel/bitcoinkernel.h>
+
+#include <functional>
+#include <memory>
+#include <span>
+#include <stdexcept>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace btck {
+
+enum class ScriptVerifyStatus : btck_ScriptVerifyStatus {
+ OK = btck_ScriptVerifyStatus_OK,
+ ERROR_INVALID_FLAGS_COMBINATION = btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION,
+ ERROR_SPENT_OUTPUTS_REQUIRED = btck_ScriptVerifyStatus_ERROR_SPENT_OUTPUTS_REQUIRED,
+};
+
+enum class ScriptVerificationFlags : btck_ScriptVerificationFlags {
+ NONE = btck_ScriptVerificationFlags_NONE,
+ P2SH = btck_ScriptVerificationFlags_P2SH,
+ DERSIG = btck_ScriptVerificationFlags_DERSIG,
+ NULLDUMMY = btck_ScriptVerificationFlags_NULLDUMMY,
+ CHECKLOCKTIMEVERIFY = btck_ScriptVerificationFlags_CHECKLOCKTIMEVERIFY,
+ CHECKSEQUENCEVERIFY = btck_ScriptVerificationFlags_CHECKSEQUENCEVERIFY,
+ WITNESS = btck_ScriptVerificationFlags_WITNESS,
+ TAPROOT = btck_ScriptVerificationFlags_TAPROOT,
+ ALL = btck_ScriptVerificationFlags_ALL
+};
+
+template <typename T>
+struct is_bitmask_enum : std::false_type {
+};
+
+template <>
+struct is_bitmask_enum<ScriptVerificationFlags> : std::true_type {
+};
+
+template <typename T>
+concept BitmaskEnum = is_bitmask_enum<T>::value;
+
+template <BitmaskEnum T>
+constexpr T operator|(T lhs, T rhs)
+{
+ return static_cast<T>(
+ static_cast<std::underlying_type_t<T>>(lhs) | static_cast<std::underlying_type_t<T>>(rhs));
+}
+
+template <BitmaskEnum T>
+constexpr T operator&(T lhs, T rhs)
+{
+ return static_cast<T>(
+ static_cast<std::underlying_type_t<T>>(lhs) & static_cast<std::underlying_type_t<T>>(rhs));
+}
+
+template <BitmaskEnum T>
+constexpr T operator^(T lhs, T rhs)
+{
+ return static_cast<T>(
+ static_cast<std::underlying_type_t<T>>(lhs) ^ static_cast<std::underlying_type_t<T>>(rhs));
+}
+
+template <BitmaskEnum T>
+constexpr T operator~(T value)
+{
+ return static_cast<T>(~static_cast<std::underlying_type_t<T>>(value));
+}
+
+template <BitmaskEnum T>
+constexpr T& operator|=(T& lhs, T rhs)
+{
+ return lhs = lhs | rhs;
+}
+
+template <BitmaskEnum T>
+constexpr T& operator&=(T& lhs, T rhs)
+{
+ return lhs = lhs & rhs;
+}
+
+template <BitmaskEnum T>
+constexpr T& operator^=(T& lhs, T rhs)
+{
+ return lhs = lhs ^ rhs;
+}
+
+template <typename T>
+T check(T ptr)
+{
+ if (ptr == nullptr) {
+ throw std::runtime_error("failed to instantiate btck object");
+ }
+ return ptr;
+}
+
+template <typename Collection, typename ValueType>
+class Iterator
+{
+public:
+ using iterator_category = std::random_access_iterator_tag;
+ using iterator_concept = std::random_access_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = ValueType;
+
+private:
+ const Collection* m_collection;
+ size_t m_idx;
+
+public:
+ Iterator() = default;
+ Iterator(const Collection* ptr) : m_collection{ptr}, m_idx{0} {}
+ Iterator(const Collection* ptr, size_t idx) : m_collection{ptr}, m_idx{idx} {}
+
+ // This is just a view, so return a copy.
+ auto operator*() const { return (*m_collection)[m_idx]; }
+ auto operator->() const { return (*m_collection)[m_idx]; }
+
+ auto& operator++() { m_idx++; return *this; }
+ auto operator++(int) { Iterator tmp = *this; ++(*this); return tmp; }
+
+ auto& operator--() { m_idx--; return *this; }
+ auto operator--(int) { auto temp = *this; --m_idx; return temp; }
+
+ auto& operator+=(difference_type n) { m_idx += n; return *this; }
+ auto& operator-=(difference_type n) { m_idx -= n; return *this; }
+
+ auto operator+(difference_type n) const { return Iterator(m_collection, m_idx + n); }
+ auto operator-(difference_type n) const { return Iterator(m_collection, m_idx - n); }
+
+ auto operator-(const Iterator& other) const { return static_cast<difference_type>(m_idx) - static_cast<difference_type>(other.m_idx); }
+
+ ValueType operator[](difference_type n) const { return (*m_collection)[m_idx + n]; }
+
+ auto operator<=>(const Iterator& other) const { return m_idx <=> other.m_idx; }
+
+ bool operator==(const Iterator& other) const { return m_collection == other.m_collection && m_idx == other.m_idx; }
+
+private:
+ friend Iterator operator+(difference_type n, const Iterator& it) { return it + n; }
+};
+
+template <typename Container, typename SizeFunc, typename GetFunc>
+concept IndexedContainer = requires(const Container& c, SizeFunc size_func, GetFunc get_func, std::size_t i) {
+ { std::invoke(size_func, c) } -> std::convertible_to<std::size_t>;
+ { std::invoke(get_func, c, i) }; // Return type is deduced
+};
+
+template <typename Container, auto SizeFunc, auto GetFunc>
+ requires IndexedContainer<Container, decltype(SizeFunc), decltype(GetFunc)>
+class Range
+{
+public:
+ using value_type = std::invoke_result_t<decltype(GetFunc), const Container&, size_t>;
+ using difference_type = std::ptrdiff_t;
+ using iterator = Iterator<Range, value_type>;
+ using const_iterator = iterator;
+
+private:
+ const Container* m_container;
+
+public:
+ explicit Range(const Container& container) : m_container(&container)
+ {
+ static_assert(std::ranges::random_access_range<Range>);
+ }
+
+ iterator begin() const { return iterator(this, 0); }
+ iterator end() const { return iterator(this, size()); }
+
+ const_iterator cbegin() const { return begin(); }
+ const_iterator cend() const { return end(); }
+
+ size_t size() const { return std::invoke(SizeFunc, *m_container); }
+
+ bool empty() const { return size() == 0; }
+
+ value_type operator[](size_t index) const { return std::invoke(GetFunc, *m_container, index); }
+
+ value_type at(size_t index) const
+ {
+ if (index >= size()) {
+ throw std::out_of_range("Index out of range");
+ }
+ return (*this)[index];
+ }
+
+ value_type front() const { return (*this)[0]; }
+ value_type back() const { return (*this)[size() - 1]; }
+};
+
+#define MAKE_RANGE_METHOD(method_name, ContainerType, SizeFunc, GetFunc, container_expr) \
+ auto method_name() const & { \
+ return Range<ContainerType, SizeFunc, GetFunc>{container_expr}; \
+ } \
+ auto method_name() const && = delete;
+
+template <typename T>
+std::vector<std::byte> write_bytes(const T* object, int (*to_bytes)(const T*, btck_WriteBytes, void*))
+{
+ std::vector<std::byte> bytes;
+ struct UserData {
+ std::vector<std::byte>* bytes;
+ std::exception_ptr exception;
+ };
+ UserData user_data = UserData{.bytes = &bytes, .exception = nullptr};
+
+ constexpr auto const write = +[](const void* buffer, size_t len, void* user_data) -> int {
+ auto& data = *reinterpret_cast<UserData*>(user_data);
+ auto& bytes = *data.bytes;
+ try {
+ auto const* first = static_cast<const std::byte*>(buffer);
+ auto const* last = first + len;
+ bytes.insert(bytes.end(), first, last);
+ return 0;
+ } catch (...) {
+ data.exception = std::current_exception();
+ return -1;
+ }
+ };
+
+ if (to_bytes(object, write, &user_data) != 0) {
+ std::rethrow_exception(user_data.exception);
+ }
+ return bytes;
+}
+
+template <typename CType>
+class View
+{
+protected:
+ const CType* m_ptr;
+
+public:
+ explicit View(const CType* ptr) : m_ptr{check(ptr)} {}
+
+ const CType* get() const { return m_ptr; }
+};
+
+template <typename CType, CType* (*CopyFunc)(const CType*), void (*DestroyFunc)(CType*)>
+class Handle
+{
+protected:
+ CType* m_ptr;
+
+public:
+ explicit Handle(CType* ptr) : m_ptr{check(ptr)} {}
+
+ // Copy constructors
+ Handle(const Handle& other)
+ : m_ptr{check(CopyFunc(other.m_ptr))} {}
+ Handle& operator=(const Handle& other)
+ {
+ if (this != &other) {
+ Handle temp(other);
+ std::swap(m_ptr, temp.m_ptr);
+ }
+ return *this;
+ }
+
+ // Move constructors
+ Handle(Handle&& other) noexcept : m_ptr(other.m_ptr) { other.m_ptr = nullptr; }
+ Handle& operator=(Handle&& other) noexcept
+ {
+ DestroyFunc(m_ptr);
+ m_ptr = std::exchange(other.m_ptr, nullptr);
+ return *this;
+ }
+
+ template <typename ViewType>
+ requires std::derived_from<ViewType, View<CType>>
+ Handle(const ViewType& view)
+ : Handle{CopyFunc(view.get())}
+ {
+ }
+
+ ~Handle() { DestroyFunc(m_ptr); }
+
+ CType* get() { return m_ptr; }
+ const CType* get() const { return m_ptr; }
+};
+
+class Transaction;
+class TransactionOutput;
+
+template <typename Derived>
+class ScriptPubkeyApi
+{
+private:
+ auto impl() const
+ {
+ return static_cast<const Derived*>(this)->get();
+ }
+
+ friend Derived;
+ ScriptPubkeyApi() = default;
+
+public:
+ bool Verify(int64_t amount,
+ const Transaction& tx_to,
+ std::span<const TransactionOutput> spent_outputs,
+ unsigned int input_index,
+ ScriptVerificationFlags flags,
+ ScriptVerifyStatus& status) const;
+
+ std::vector<std::byte> ToBytes() const
+ {
+ return write_bytes(impl(), btck_script_pubkey_to_bytes);
+ }
+};
+
+class ScriptPubkeyView : public View<btck_ScriptPubkey>, public ScriptPubkeyApi<ScriptPubkeyView>
+{
+public:
+ explicit ScriptPubkeyView(const btck_ScriptPubkey* ptr) : View{ptr} {}
+};
+
+class ScriptPubkey : public Handle<btck_ScriptPubkey, btck_script_pubkey_copy, btck_script_pubkey_destroy>, public ScriptPubkeyApi<ScriptPubkey>
+{
+public:
+ explicit ScriptPubkey(std::span<const std::byte> raw)
+ : Handle{btck_script_pubkey_create(raw.data(), raw.size())} {}
+
+ ScriptPubkey(const ScriptPubkeyView& view)
+ : Handle(view) {}
+};
+
+template <typename Derived>
+class TransactionOutputApi
+{
+private:
+ auto impl() const
+ {
+ return static_cast<const Derived*>(this)->get();
+ }
+
+ friend Derived;
+ TransactionOutputApi() = default;
+
+public:
+ int64_t Amount() const
+ {
+ return btck_transaction_output_get_amount(impl());
+ }
+
+ ScriptPubkeyView GetScriptPubkey() const
+ {
+ return ScriptPubkeyView{btck_transaction_output_get_script_pubkey(impl())};
+ }
+};
+
+class TransactionOutputView : public View<btck_TransactionOutput>, public TransactionOutputApi<TransactionOutputView>
+{
+public:
+ explicit TransactionOutputView(const btck_TransactionOutput* ptr) : View{ptr} {}
+};
+
+class TransactionOutput : public Handle<btck_TransactionOutput, btck_transaction_output_copy, btck_transaction_output_destroy>, public TransactionOutputApi<TransactionOutput>
+{
+public:
+ explicit TransactionOutput(const ScriptPubkey& script_pubkey, int64_t amount)
+ : Handle{btck_transaction_output_create(script_pubkey.get(), amount)} {}
+
+ TransactionOutput(const TransactionOutputView& view)
+ : Handle(view) {}
+};
+
+template <typename Derived>
+class TransactionApi
+{
+private:
+ auto impl() const
+ {
+ return static_cast<const Derived*>(this)->get();
+ }
+
+public:
+ size_t CountOutputs() const
+ {
+ return btck_transaction_count_outputs(impl());
+ }
+
+ size_t CountInputs() const
+ {
+ return btck_transaction_count_inputs(impl());
+ }
+
+ TransactionOutputView GetOutput(size_t index) const
+ {
+ return TransactionOutputView{btck_transaction_get_output_at(impl(), index)};
+ }
+
+ MAKE_RANGE_METHOD(Outputs, Derived, &TransactionApi<Derived>::CountOutputs, &TransactionApi<Derived>::GetOutput, *static_cast<const Derived*>(this))
+
+ std::vector<std::byte> ToBytes() const
+ {
+ return write_bytes(impl(), btck_transaction_to_bytes);
+ }
+};
+
+class Transaction : public Handle<btck_Transaction, btck_transaction_copy, btck_transaction_destroy>, public TransactionApi<Transaction>
+{
+public:
+ explicit Transaction(std::span<const std::byte> raw_transaction)
+ : Handle{btck_transaction_create(raw_transaction.data(), raw_transaction.size())} {}
+};
+
+template <typename Derived>
+bool ScriptPubkeyApi<Derived>::Verify(int64_t amount,
+ const Transaction& tx_to,
+ const std::span<const TransactionOutput> spent_outputs,
+ unsigned int input_index,
+ ScriptVerificationFlags flags,
+ ScriptVerifyStatus& status) const
+{
+ const btck_TransactionOutput** spent_outputs_ptr = nullptr;
+ std::vector<const btck_TransactionOutput*> raw_spent_outputs;
+ if (spent_outputs.size() > 0) {
+ raw_spent_outputs.reserve(spent_outputs.size());
+
+ for (const auto& output : spent_outputs) {
+ raw_spent_outputs.push_back(output.get());
+ }
+ spent_outputs_ptr = raw_spent_outputs.data();
+ }
+ auto result = btck_script_pubkey_verify(
+ impl(),
+ amount,
+ tx_to.get(),
+ spent_outputs_ptr, spent_outputs.size(),
+ input_index,
+ static_cast<btck_ScriptVerificationFlags>(flags),
+ reinterpret_cast<btck_ScriptVerifyStatus*>(&status));
+ return result == 1;
+}
+
+} // namespace btck
+
+#endif // BITCOIN_KERNEL_BITCOINKERNEL_WRAPPER_H
diff --git a/src/test/kernel/CMakeLists.txt b/src/test/kernel/CMakeLists.txt
new file mode 100644
index 00000000..17900bdd
--- /dev/null
+++ b/src/test/kernel/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_executable(test_kernel
+ test_kernel.cpp
+)
+
+target_link_libraries(test_kernel
+ PRIVATE
+ core_interface
+ bitcoinkernel
+ Boost::headers
+)
+
+set_target_properties(test_kernel PROPERTIES
+ SKIP_BUILD_RPATH OFF
+)
+
+add_test(NAME test_kernel COMMAND test_kernel)
diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
new file mode 100644
index 00000000..632a01d0
--- /dev/null
+++ b/src/test/kernel/test_kernel.cpp
@@ -0,0 +1,346 @@
+// Copyright (c) 2024-present The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <kernel/bitcoinkernel.h>
+#include <kernel/bitcoinkernel_wrapper.h>
+
+#define BOOST_TEST_MODULE Bitcoin Kernel Test Suite
+#include <boost/test/included/unit_test.hpp>
+
+#include <charconv>
+#include <cstdint>
+#include <cstdlib>
+#include <iostream>
+#include <ranges>
+#include <span>
+#include <vector>
+
+using namespace btck;
+
+std::vector<std::byte> hex_string_to_byte_vec(std::string_view hex)
+{
+ std::vector<std::byte> bytes;
+ bytes.reserve(hex.length() / 2);
+
+ for (size_t i{0}; i < hex.length(); i += 2) {
+ uint8_t byte_value;
+ auto [ptr, ec] = std::from_chars(hex.data() + i, hex.data() + i + 2, byte_value, 16);
+
+ if (ec != std::errc{} || ptr != hex.data() + i + 2) {
+ throw std::invalid_argument("Invalid hex character");
+ }
+ bytes.push_back(static_cast<std::byte>(byte_value));
+ }
+ return bytes;
+}
+
+constexpr auto VERIFY_ALL_PRE_SEGWIT{ScriptVerificationFlags::P2SH | ScriptVerificationFlags::DERSIG |
+ ScriptVerificationFlags::NULLDUMMY | ScriptVerificationFlags::CHECKLOCKTIMEVERIFY |
+ ScriptVerificationFlags::CHECKSEQUENCEVERIFY};
+constexpr auto VERIFY_ALL_PRE_TAPROOT{VERIFY_ALL_PRE_SEGWIT | ScriptVerificationFlags::WITNESS};
+
+void check_equal(std::span<const std::byte> _actual, std::span<const std::byte> _expected, bool equal = true)
+{
+ std::span<const uint8_t> actual{reinterpret_cast<const unsigned char*>(_actual.data()), _actual.size()};
+ std::span<const uint8_t> expected{reinterpret_cast<const unsigned char*>(_expected.data()), _expected.size()};
+ BOOST_CHECK_EQUAL_COLLECTIONS(
+ actual.begin(), actual.end(),
+ expected.begin(), expected.end());
+}
+
+void run_verify_test(
+ const ScriptPubkey& spent_script_pubkey,
+ const Transaction& spending_tx,
+ std::span<TransactionOutput> spent_outputs,
+ int64_t amount,
+ unsigned int input_index,
+ bool taproot)
+{
+ auto status = ScriptVerifyStatus::OK;
+
+ if (taproot) {
+ BOOST_CHECK(spent_script_pubkey.Verify(
+ amount,
+ spending_tx,
+ spent_outputs,
+ input_index,
+ ScriptVerificationFlags::ALL,
+ status));
+ BOOST_CHECK(status == ScriptVerifyStatus::OK);
+ } else {
+ BOOST_CHECK(!spent_script_pubkey.Verify(
+ amount,
+ spending_tx,
+ spent_outputs,
+ input_index,
+ ScriptVerificationFlags::ALL,
+ status));
+ BOOST_CHECK(status == ScriptVerifyStatus::ERROR_SPENT_OUTPUTS_REQUIRED);
+ }
+
+ BOOST_CHECK(spent_script_pubkey.Verify(
+ amount,
+ spending_tx,
+ spent_outputs,
+ input_index,
+ VERIFY_ALL_PRE_TAPROOT,
+ status));
+ BOOST_CHECK(status == ScriptVerifyStatus::OK);
+
+ BOOST_CHECK(spent_script_pubkey.Verify(
+ 0,
+ spending_tx,
+ spent_outputs,
+ input_index,
+ VERIFY_ALL_PRE_SEGWIT,
+ status));
+ BOOST_CHECK(status == ScriptVerifyStatus::OK);
+}
+
+template <typename T>
+concept HasToBytes = requires(T t) {
+ { t.ToBytes() } -> std::convertible_to<std::vector<std::byte>>;
+};
+
+template <typename T>
+void CheckHandle(T object, T distinct_object)
+{
+ BOOST_CHECK(object.get() != nullptr);
+ BOOST_CHECK(distinct_object.get() != nullptr);
+ BOOST_CHECK(object.get() != distinct_object.get());
+
+ if constexpr (HasToBytes<T>) {
+ BOOST_CHECK_NE(object.ToBytes().size(), distinct_object.ToBytes().size());
+ }
+
+ // Copy constructor
+ T object2(distinct_object);
+ BOOST_CHECK_NE(distinct_object.get(), object2.get());
+ if constexpr (HasToBytes<T>) {
+ check_equal(distinct_object.ToBytes(), object2.ToBytes());
+ }
+
+ // Copy assignment
+ T object3{distinct_object};
+ object2 = object3;
+ BOOST_CHECK_NE(object3.get(), object2.get());
+ if constexpr (HasToBytes<T>) {
+ check_equal(object3.ToBytes(), object2.ToBytes());
+ }
+
+ // Move constructor
+ auto* original_ptr = object2.get();
+ T object4{std::move(object2)};
+ BOOST_CHECK_EQUAL(object4.get(), original_ptr);
+ BOOST_CHECK_EQUAL(object2.get(), nullptr); // NOLINT(bugprone-use-after-move)
+ if constexpr (HasToBytes<T>) {
+ check_equal(object4.ToBytes(), object3.ToBytes());
+ }
+
+ // Move assignment
+ original_ptr = object4.get();
+ object2 = std::move(object4);
+ BOOST_CHECK_EQUAL(object2.get(), original_ptr);
+ BOOST_CHECK_EQUAL(object4.get(), nullptr); // NOLINT(bugprone-use-after-move)
+ if constexpr (HasToBytes<T>) {
+ check_equal(object2.ToBytes(), object3.ToBytes());
+ }
+}
+
+template <typename RangeType>
+ requires std::ranges::random_access_range<RangeType>
+void CheckRange(const RangeType& range, size_t expected_size)
+{
+ using value_type = std::ranges::range_value_t<RangeType>;
+
+ BOOST_CHECK_EQUAL(range.size(), expected_size);
+ BOOST_CHECK_EQUAL(range.empty(), (expected_size == 0));
+
+ BOOST_CHECK(range.begin() != range.end());
+ BOOST_CHECK_EQUAL(std::distance(range.begin(), range.end()), static_cast<std::ptrdiff_t>(expected_size));
+ BOOST_CHECK(range.cbegin() == range.begin());
+ BOOST_CHECK(range.cend() == range.end());
+
+ for (size_t i = 0; i < range.size(); ++i) {
+ BOOST_CHECK_EQUAL(range[i].get(), (*(range.begin() + i)).get());
+ }
+
+ BOOST_CHECK_NE(range.at(0).get(), range.at(expected_size - 1).get());
+ BOOST_CHECK_THROW(range.at(expected_size), std::out_of_range);
+
+ BOOST_CHECK_EQUAL(range.front().get(), range[0].get());
+ BOOST_CHECK_EQUAL(range.back().get(), range[expected_size - 1].get());
+
+ auto it = range.begin();
+ auto it_copy = it;
+ ++it;
+ BOOST_CHECK(it != it_copy);
+ --it;
+ BOOST_CHECK(it == it_copy);
+ it = range.begin();
+ auto old_it = it++;
+ BOOST_CHECK(old_it == range.begin());
+ BOOST_CHECK(it == range.begin() + 1);
+ old_it = it--;
+ BOOST_CHECK(old_it == range.begin() + 1);
+ BOOST_CHECK(it == range.begin());
+
+ it = range.begin();
+ it += 2;
+ BOOST_CHECK(it == range.begin() + 2);
+ it -= 2;
+ BOOST_CHECK(it == range.begin());
+
+ BOOST_CHECK(range.begin() < range.end());
+ BOOST_CHECK(range.begin() <= range.end());
+ BOOST_CHECK(range.end() > range.begin());
+ BOOST_CHECK(range.end() >= range.begin());
+ BOOST_CHECK(range.begin() == range.begin());
+
+ BOOST_CHECK_EQUAL(range.begin()[0].get(), range[0].get());
+
+ size_t count = 0;
+ for (auto rit = range.end(); rit != range.begin();) {
+ --rit;
+ ++count;
+ }
+ BOOST_CHECK_EQUAL(count, expected_size);
+
+ std::vector<value_type> collected;
+ for (const auto& elem : range) {
+ collected.push_back(elem);
+ }
+ BOOST_CHECK_EQUAL(collected.size(), expected_size);
+
+ BOOST_CHECK_EQUAL(std::ranges::size(range), expected_size);
+
+ it = range.begin();
+ auto it2 = 1 + it;
+ BOOST_CHECK(it2 == it + 1);
+}
+
+BOOST_AUTO_TEST_CASE(btck_transaction_tests)
+{
+ auto tx_data{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")};
+ auto tx{Transaction{tx_data}};
+ auto tx_data_2{hex_string_to_byte_vec("02000000000101904f4ee5c87d20090b642f116e458cd6693292ad9ece23e72f15fb6c05b956210500000000fdffffff02e2010000000000002251200839a723933b56560487ec4d67dda58f09bae518ffa7e148313c5696ac837d9f10060000000000002251205826bcdae7abfb1c468204170eab00d887b61ab143464a4a09e1450bdc59a3340140f26e7af574e647355830772946356c27e7bbc773c5293688890f58983499581be84de40be7311a14e6d6422605df086620e75adae84ff06b75ce5894de5e994a00000000")};
+ auto tx2{Transaction{tx_data_2}};
+ CheckHandle(tx, tx2);
+
+ BOOST_CHECK_EQUAL(tx.CountOutputs(), 2);
+ BOOST_CHECK_EQUAL(tx.CountInputs(), 1);
+ auto broken_tx_data{std::span<std::byte>{tx_data.begin(), tx_data.begin() + 10}};
+ BOOST_CHECK_THROW(Transaction{broken_tx_data}, std::runtime_error);
+ auto output{tx.GetOutput(tx.CountOutputs() - 1)};
+ BOOST_CHECK_EQUAL(output.Amount(), 42130042);
+ auto script_pubkey{output.GetScriptPubkey()};
+ {
+ auto tx_new{Transaction{tx_data}};
+ // This is safe, because we now use copy assignment
+ TransactionOutput output = tx_new.GetOutput(tx_new.CountOutputs() - 1);
+ ScriptPubkey script = output.GetScriptPubkey();
+
+ TransactionOutputView output2 = tx_new.GetOutput(tx_new.CountOutputs() - 1);
+ BOOST_CHECK_NE(output.get(), output2.get());
+ BOOST_CHECK_EQUAL(output.Amount(), output2.Amount());
+ TransactionOutput output3 = output2;
+ BOOST_CHECK_NE(output3.get(), output2.get());
+ BOOST_CHECK_EQUAL(output3.Amount(), output2.Amount());
+
+ // Non-owned view
+ ScriptPubkeyView script2 = output.GetScriptPubkey();
+ BOOST_CHECK_NE(script.get(), script2.get());
+ check_equal(script.ToBytes(), script2.ToBytes());
+
+ // Non-owned to owned
+ ScriptPubkey script3 = script2;
+ BOOST_CHECK_NE(script3.get(), script2.get());
+ check_equal(script3.ToBytes(), script2.ToBytes());
+ }
+ BOOST_CHECK_EQUAL(output.Amount(), 42130042);
+
+ auto tx_roundtrip{Transaction{tx.ToBytes()}};
+ check_equal(tx_roundtrip.ToBytes(), tx_data);
+
+ // The following code is unsafe, but left here to show limitations of the
+ // API, because we preserve the output view beyond the lifetime of the
+ // transaction. The view type wrapper should make this clear to the user.
+ // auto get_output = [&]() -> TransactionOutputView {
+ // auto tx{Transaction{tx_data}};
+ // return tx.GetOutput(0);
+ // };
+ // auto output_new = get_output();
+ // BOOST_CHECK_EQUAL(output_new.Amount(), 20737411);
+
+ int64_t total_amount{0};
+ for (const auto output : tx.Outputs()) {
+ total_amount += output.Amount();
+ }
+ BOOST_CHECK_EQUAL(total_amount, 62867453);
+
+ auto amount = *(tx.Outputs() | std::ranges::views::filter([](const auto& output) {
+ return output.Amount() == 42130042;
+ }) |
+ std::views::transform([](const auto& output) {
+ return output.Amount();
+ })).begin();
+ BOOST_REQUIRE(amount);
+ BOOST_CHECK_EQUAL(amount, 42130042);
+
+ CheckRange(tx.Outputs(), tx.CountOutputs());
+
+ ScriptPubkey script_pubkey_roundtrip{script_pubkey.ToBytes()};
+ check_equal(script_pubkey_roundtrip.ToBytes(), script_pubkey.ToBytes());
+}
+
+BOOST_AUTO_TEST_CASE(btck_script_pubkey)
+{
+ auto script_data{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")};
+ std::vector<std::byte> script_data_2 = script_data;
+ script_data_2.push_back(std::byte{0x51});
+ ScriptPubkey script{script_data};
+ ScriptPubkey script2{script_data_2};
+ CheckHandle(script, script2);
+}
+
+BOOST_AUTO_TEST_CASE(btck_transaction_output)
+{
+ ScriptPubkey script{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")};
+ TransactionOutput output{script, 1};
+ TransactionOutput output2{script, 2};
+ CheckHandle(output, output2);
+}
+
+BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
+{
+ // Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d
+ run_verify_test(
+ /*spent_script_pubkey*/ ScriptPubkey{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")},
+ /*spending_tx*/ Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")},
+ /*spent_outputs*/ {},
+ /*amount*/ 0,
+ /*input_index*/ 0,
+ /*is_taproot*/ false);
+
+ // Segwit transaction 1a3e89644985fbbb41e0dcfe176739813542b5937003c46a07de1e3ee7a4a7f3
+ run_verify_test(
+ /*spent_script_pubkey*/ ScriptPubkey{hex_string_to_byte_vec("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d")},
+ /*spending_tx*/ Transaction{hex_string_to_byte_vec("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000")},
+ /*spent_outputs*/ {},
+ /*amount*/ 18393430,
+ /*input_index*/ 0,
+ /*is_taproot*/ false);
+
+ // Taproot transaction 33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036
+ auto taproot_spent_script_pubkey{ScriptPubkey{hex_string_to_byte_vec("5120339ce7e165e67d93adb3fef88a6d4beed33f01fa876f05a225242b82a631abc0")}};
+ std::vector<TransactionOutput> spent_outputs;
+ spent_outputs.emplace_back(taproot_spent_script_pubkey, 88480);
+ run_verify_test(
+ /*spent_script_pubkey*/ taproot_spent_script_pubkey,
+ /*spending_tx*/ Transaction{hex_string_to_byte_vec("01000000000101d1f1c1f8cdf6759167b90f52c9ad358a369f95284e841d7a2536cef31c0549580100000000fdffffff020000000000000000316a2f49206c696b65205363686e6f7272207369677320616e6420492063616e6e6f74206c69652e204062697462756734329e06010000000000225120a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e5412328f90140a60c383f71bac0ec919b1d7dbc3eb72dd56e7aa99583615564f9f99b8ae4e837b758773a5b2e4c51348854c8389f008e05029db7f464a5ff2e01d5e6e626174affd30a00")},
+ /*spent_outputs*/ spent_outputs,
+ /*amount*/ 88480,
+ /*input_index*/ 0,
+ /*is_taproot*/ true);
+}
Why this scored 26/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.