From e6de3a2d3c78d5b72f57bd6a5d711fed97ca70c1 Mon Sep 17 00:00:00 2001 From: Peter Zafonte Date: Fri, 3 Jul 2026 13:29:42 -0400 Subject: [PATCH 1/2] kernel: expose witness stack for btck_TransactionInput --- src/kernel/bitcoinkernel.cpp | 28 +++++++++++++ src/kernel/bitcoinkernel.h | 66 +++++++++++++++++++++++++++++- src/kernel/bitcoinkernel_wrapper.h | 49 ++++++++++++++++++++++ src/test/kernel/test_kernel.cpp | 22 ++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 0a315ff39b3..92700d162c2 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -498,6 +498,7 @@ struct btck_TransactionSpentOutputs : Handle {}; struct btck_BlockHash : Handle {}; struct btck_TransactionInput : Handle {}; +struct btck_WitnessStack : Handle {}; struct btck_TransactionOutPoint: Handle {}; struct btck_Txid: Handle {}; struct btck_PrecomputedTransactionData : Handle {}; @@ -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 6ce65a04378..09b1caf08b9 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 ff7899d1d69..f84a06edd2d 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -564,6 +564,49 @@ public: : Handle(view) {} }; +template +class WitnessStackApi +{ +private: + auto impl() const + { + return static_cast(this)->get(); + } + + friend Derived; + WitnessStackApi() = default; + +public: + size_t CountItems() const + { + return btck_witness_stack_count_items(impl()); + } + + std::vector 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::CountItems, &WitnessStackApi::GetItem, *static_cast(this)) +}; + +class WitnessStackView : public View, public WitnessStackApi +{ +public: + explicit WitnessStackView(const btck_WitnessStack* ptr) : View{ptr} {} +}; + +class WitnessStack : public Handle, public WitnessStackApi +{ +public: + WitnessStack(const WitnessStackView& view) + : Handle(view) {} +}; + template 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, public TransactionInputApi diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index e6d215dc578..8dc63fd606b 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) { From 6667dc4eec7b458893920eb7f8631bdf2467c046 Mon Sep 17 00:00:00 2001 From: Peter Zafonte Date: Fri, 3 Jul 2026 13:36:21 -0400 Subject: [PATCH 2/2] kernel: expose scriptSig for btck_TransactionInput --- src/kernel/bitcoinkernel.cpp | 6 ++++++ src/kernel/bitcoinkernel.h | 14 ++++++++++++++ src/kernel/bitcoinkernel_wrapper.h | 4 ++++ src/test/kernel/test_kernel.cpp | 5 +++++ 4 files changed, 29 insertions(+) diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp index 92700d162c2..8967476569d 100644 --- a/src/kernel/bitcoinkernel.cpp +++ b/src/kernel/bitcoinkernel.cpp @@ -715,6 +715,12 @@ const btck_WitnessStack* btck_transaction_input_get_witness_stack(const btck_Tra return btck_WitnessStack::ref(&btck_TransactionInput::get(input).scriptWitness); } +int btck_transaction_input_get_script_sig(const btck_TransactionInput* input, btck_WriteBytes writer, void* user_data) +{ + const auto& script_sig{btck_TransactionInput::get(input).scriptSig}; + return writer(script_sig.data(), script_sig.size(), user_data); +} + void btck_transaction_input_destroy(btck_TransactionInput* input) { delete input; diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h index 09b1caf08b9..b22281653b6 100644 --- a/src/kernel/bitcoinkernel.h +++ b/src/kernel/bitcoinkernel.h @@ -1697,6 +1697,20 @@ BITCOINKERNEL_API uint32_t btck_transaction_input_get_sequence( BITCOINKERNEL_API const btck_WitnessStack* btck_transaction_input_get_witness_stack( const btck_TransactionInput* transaction_input) BITCOINKERNEL_ARG_NONNULL(1); +/** + * @brief Serialize the script sig of a transaction input through the passed + * in callback. + * + * @param[in] transaction_input Non-null. + * @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. + */ +BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_transaction_input_get_script_sig( + const btck_TransactionInput* transaction_input, + btck_WriteBytes writer, + void* user_data) BITCOINKERNEL_ARG_NONNULL(1, 2); + /** * Destroy the transaction input. */ diff --git a/src/kernel/bitcoinkernel_wrapper.h b/src/kernel/bitcoinkernel_wrapper.h index f84a06edd2d..b5bee731c56 100644 --- a/src/kernel/bitcoinkernel_wrapper.h +++ b/src/kernel/bitcoinkernel_wrapper.h @@ -635,6 +635,10 @@ public: return WitnessStackView{btck_transaction_input_get_witness_stack(impl())}; } + std::vector GetScriptSig() const + { + return write_bytes(impl(), btck_transaction_input_get_script_sig); + } }; class TransactionInputView : public View, public TransactionInputApi diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp index 8dc63fd606b..f156dc974b2 100644 --- a/src/test/kernel/test_kernel.cpp +++ b/src/test/kernel/test_kernel.cpp @@ -518,6 +518,10 @@ BOOST_AUTO_TEST_CASE(btck_transaction_input) BOOST_CHECK_EQUAL(ws_0.CountItems(), 0); BOOST_CHECK(ws_0.Items().empty()); + // P2PKH: DER sig + compressed pubkey push. + BOOST_CHECK(input_0.GetScriptSig() == hex_string_to_byte_vec("473044022004893432347f39beaa280e99da595681ddb20fc45010176897e6e055d716dbfa022040a9e46648a5d10c33ef7cee5e6cf4b56bd513eae3ae044f0039824b02d0f44c012102982331a52822fd9b62e9b5d120da1d248558fac3da3a3c51cd7d9c8ad3da760e")); + BOOST_CHECK(input_1.GetScriptSig() == hex_string_to_byte_vec("473044022068bcedc7fe39c9f21ad318df2c2da62c2dc9522a89c28c8420ff9d03d2e6bf7b0220132afd752754e5cb1ea2fd0ed6a38ec666781e34b0e93dc9a08f2457842cf5660121033aeb9c079ea3e08ea03556182ab520ce5c22e6b0cb95cee6435ee17144d860cd")); + // 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); @@ -535,6 +539,7 @@ BOOST_AUTO_TEST_CASE(btck_transaction_input) WitnessStack owned_ws_0{ws_0}; WitnessStack owned_ws{ws}; CheckHandle(owned_ws_0, owned_ws); + BOOST_CHECK(segwit_input.GetScriptSig().empty()); } BOOST_AUTO_TEST_CASE(btck_precomputed_txdata) {