kernel: expose witness stack for btck_TransactionInput
What changed, and why it matters
This commit adds a new public read-only API to the Bitcoin Core 'kernel' library so that outside programs can inspect the 'witness stack' of a transaction input. The witness stack is part of how modern Bitcoin transactions (SegWit/Taproot) carry signatures and scripts. There is no bug fix, behavior change, or security patch here—only a new way to read data that was already stored internally. The change is accompanied by new tests.
No security action required. Treat as a normal feature/API addition. Reviewers may want to confirm that the lifetime documentation ('returned witness stack is not owned and depends on the lifetime of the transaction input') is clear to API consumers, but this is a documentation/usability concern, not a security defect.
Security signals we found
No security-relevant signals present in the diff or commit message.
Change is purely an API surface expansion for read-only access to existing data.
No memory-unsafe patterns introduced: uses existing opaque-handle wrappers, copy/destroy symmetry, and asserts on index bounds.
Evidence from the diff
The patch exposes CScriptWitness through the bitcoinkernel C API and C++ wrapper. It introduces btck_WitnessStack, btck_transaction_input_get_witness_stack, btck_witness_stack_count_items, btck_witness_stack_get_item_at, btck_witness_stack_copy, and btck_witness_stack_destroy. The implementation uses the existing Handle/View pattern, returns a non-owning reference from a transaction input, and copies on demand. A unit test verifies counts and contents for both an empty witness and a real P2WSH transaction. No consensus, validation, or networking code is modified.
Changed components
src/kernel/bitcoinkernel.cppsrc/kernel/bitcoinkernel.hsrc/kernel/bitcoinkernel_wrapper.hsrc/test/kernel/test_kernel.cppInspect captured patch +164 / −1
diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
index 0a315ff3..92700d16 100644
--- a/src/kernel/bitcoinkernel.cpp
+++ b/src/kernel/bitcoinkernel.cpp
@@ -498,6 +498,7 @@ struct btck_TransactionSpentOutputs : Handle<btck_TransactionSpentOutputs, CTxUn
struct btck_Coin : Handle<btck_Coin, Coin> {};
struct btck_BlockHash : Handle<btck_BlockHash, uint256> {};
struct btck_TransactionInput : Handle<btck_TransactionInput, CTxIn> {};
+struct btck_WitnessStack : Handle<btck_WitnessStack, CScriptWitness> {};
struct btck_TransactionOutPoint: Handle<btck_TransactionOutPoint, COutPoint> {};
struct btck_Txid: Handle<btck_Txid, Txid> {};
struct btck_PrecomputedTransactionData : Handle<btck_PrecomputedTransactionData, PrecomputedTransactionData> {};
@@ -709,11 +710,38 @@ uint32_t btck_transaction_input_get_sequence(const btck_TransactionInput* input)
return btck_TransactionInput::get(input).nSequence;
}
+const btck_WitnessStack* btck_transaction_input_get_witness_stack(const btck_TransactionInput* input)
+{
+ return btck_WitnessStack::ref(&btck_TransactionInput::get(input).scriptWitness);
+}
+
void btck_transaction_input_destroy(btck_TransactionInput* input)
{
delete input;
}
+size_t btck_witness_stack_count_items(const btck_WitnessStack* witness_stack)
+{
+ return btck_WitnessStack::get(witness_stack).stack.size();
+}
+
+int btck_witness_stack_get_item_at(const btck_WitnessStack* witness_stack, size_t index, btck_WriteBytes writer, void* user_data)
+{
+ const auto& stack{btck_WitnessStack::get(witness_stack).stack};
+ assert(index < stack.size());
+ return writer(stack[index].data(), stack[index].size(), user_data);
+}
+
+btck_WitnessStack* btck_witness_stack_copy(const btck_WitnessStack* witness_stack)
+{
+ return btck_WitnessStack::copy(witness_stack);
+}
+
+void btck_witness_stack_destroy(btck_WitnessStack* witness_stack)
+{
+ delete witness_stack;
+}
+
btck_TransactionOutPoint* btck_transaction_out_point_copy(const btck_TransactionOutPoint* out_point)
{
return btck_TransactionOutPoint::copy(out_point);
diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
index 6ce65a04..09b1caf0 100644
--- a/src/kernel/bitcoinkernel.h
+++ b/src/kernel/bitcoinkernel.h
@@ -298,10 +298,17 @@ typedef struct btck_BlockHash btck_BlockHash;
/**
* Opaque data structure for holding a transaction input.
*
- * Holds information on the @ref btck_TransactionOutPoint held within.
+ * Holds information on the @ref btck_TransactionOutPoint, @ref btck_WitnessStack and script_sig held within.
*/
typedef struct btck_TransactionInput btck_TransactionInput;
+/**
+ * Opaque data structure for holding a witness stack.
+ *
+ * Holds a sequence of witness stack items.
+ */
+typedef struct btck_WitnessStack btck_WitnessStack;
+
/**
* Opaque data structure for holding a transaction out point.
*
@@ -1680,6 +1687,16 @@ BITCOINKERNEL_API const btck_TransactionOutPoint* btck_transaction_input_get_out
BITCOINKERNEL_API uint32_t btck_transaction_input_get_sequence(
const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);
+/**
+ * @brief Get the witness stack of a transaction input. The returned witness
+ * stack is not owned and depends on the lifetime of the transaction input.
+ *
+ * @param[in] transaction_input Non-null.
+ * @return The witness stack.
+ */
+BITCOINKERNEL_API const btck_WitnessStack* btck_transaction_input_get_witness_stack(
+ const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1);
+
/**
* Destroy the transaction input.
*/
@@ -1687,6 +1704,53 @@ BITCOINKERNEL_API void btck_transaction_input_destroy(btck_TransactionInput* tra
///@}
+/** @name Witness Stack
+ * Functions for working with witness stacks.
+ */
+///@{
+
+/**
+ * @brief Return the number of items in a witness stack.
+ *
+ * @param[in] witness_stack Non-null.
+ * @return The number of witness stack items.
+ */
+BITCOINKERNEL_API size_t btck_witness_stack_count_items(
+ const btck_WitnessStack* witness_stack) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * @brief Serialize a witness stack item at a given index through the passed in
+ * callback.
+ *
+ * @param[in] witness_stack Non-null.
+ * @param[in] index Index of the item in the witness stack.
+ * @param[in] writer Non-null, function pointer for writing bytes.
+ * @param[in] user_data Nullable, passed back through the writer callback.
+ * @return The return value of the writer.
+ * @pre index < btck_witness_stack_count_items(witness_stack)
+ */
+BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_witness_stack_get_item_at(
+ const btck_WitnessStack* witness_stack,
+ size_t index,
+ btck_WriteBytes writer,
+ void* user_data) BITCOINKERNEL_ARG_NONNULL(1, 3);
+
+/**
+ * @brief Copy a witness stack.
+ *
+ * @param[in] witness_stack Non-null.
+ * @return The copied witness stack.
+ */
+BITCOINKERNEL_API btck_WitnessStack* BITCOINKERNEL_WARN_UNUSED_RESULT btck_witness_stack_copy(
+ const btck_WitnessStack* witness_stack) BITCOINKERNEL_ARG_NONNULL(1);
+
+/**
+ * Destroy the witness stack.
+ */
+BITCOINKERNEL_API void btck_witness_stack_destroy(btck_WitnessStack* witness_stack);
+
+///@}
+
/** @name Transaction Out Point
* Functions for working with transaction out points.
*/
diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h
index ff7899d1..f84a06ed 100644
--- a/src/kernel/bitcoinkernel_wrapper.h
+++ b/src/kernel/bitcoinkernel_wrapper.h
@@ -564,6 +564,49 @@ public:
: Handle(view) {}
};
+template <typename Derived>
+class WitnessStackApi
+{
+private:
+ auto impl() const
+ {
+ return static_cast<const Derived*>(this)->get();
+ }
+
+ friend Derived;
+ WitnessStackApi() = default;
+
+public:
+ size_t CountItems() const
+ {
+ return btck_witness_stack_count_items(impl());
+ }
+
+ std::vector<std::byte> GetItem(size_t index) const
+ {
+ struct Item { const btck_WitnessStack* stack; size_t index; };
+ Item item{impl(), index};
+ return write_bytes(&item, +[](const Item* c, btck_WriteBytes w, void* ud) {
+ return btck_witness_stack_get_item_at(c->stack, c->index, w, ud);
+ });
+ }
+
+ MAKE_RANGE_METHOD(Items, Derived, &WitnessStackApi<Derived>::CountItems, &WitnessStackApi<Derived>::GetItem, *static_cast<const Derived*>(this))
+};
+
+class WitnessStackView : public View<btck_WitnessStack>, public WitnessStackApi<WitnessStackView>
+{
+public:
+ explicit WitnessStackView(const btck_WitnessStack* ptr) : View{ptr} {}
+};
+
+class WitnessStack : public Handle<btck_WitnessStack, btck_witness_stack_copy, btck_witness_stack_destroy>, public WitnessStackApi<WitnessStack>
+{
+public:
+ WitnessStack(const WitnessStackView& view)
+ : Handle(view) {}
+};
+
template <typename Derived>
class TransactionInputApi
{
@@ -586,6 +629,12 @@ public:
{
return btck_transaction_input_get_sequence(impl());
}
+
+ WitnessStackView GetWitnessStack() const
+ {
+ return WitnessStackView{btck_transaction_input_get_witness_stack(impl())};
+ }
+
};
class TransactionInputView : public View<btck_TransactionInput>, public TransactionInputApi<TransactionInputView>
diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
index e6d215dc..8dc63fd6 100644
--- a/src/test/kernel/test_kernel.cpp
+++ b/src/test/kernel/test_kernel.cpp
@@ -513,6 +513,28 @@ BOOST_AUTO_TEST_CASE(btck_transaction_input)
OutPoint point_0 = input_0.OutPoint();
OutPoint point_1 = input_1.OutPoint();
CheckHandle(point_0, point_1);
+
+ WitnessStackView ws_0 = input_0.GetWitnessStack();
+ BOOST_CHECK_EQUAL(ws_0.CountItems(), 0);
+ BOOST_CHECK(ws_0.Items().empty());
+
+ // P2WSH input: OP_0, sig, sig, redeem_script (0, 71, 71, 105 bytes); no scriptSig.
+ Transaction segwit_tx{hex_string_to_byte_vec("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000")};
+ TransactionInputView segwit_input = segwit_tx.GetInput(0);
+ WitnessStackView ws = segwit_input.GetWitnessStack();
+ BOOST_CHECK_EQUAL(ws.CountItems(), 4);
+ BOOST_CHECK(ws.GetItem(0).empty());
+ BOOST_CHECK(ws.GetItem(1) == hex_string_to_byte_vec("30440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc01"));
+ BOOST_CHECK(ws.GetItem(2) == hex_string_to_byte_vec("30440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd435501"));
+ BOOST_CHECK(ws.GetItem(3) == hex_string_to_byte_vec("52210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae"));
+ auto items = ws.Items();
+ BOOST_CHECK_EQUAL(items.size(), 4);
+ for (size_t i = 0; i < items.size(); ++i) {
+ BOOST_CHECK(items[i] == ws.GetItem(i));
+ }
+ WitnessStack owned_ws_0{ws_0};
+ WitnessStack owned_ws{ws};
+ CheckHandle(owned_ws_0, owned_ws);
}
BOOST_AUTO_TEST_CASE(btck_precomputed_txdata) {
Why this scored 15/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.