kernel: Expose reusable PrecomputedTransactionData in script validation #33891

pull joshdoman wants to merge 1 commits into bitcoin:master from joshdoman:kernel/precomputed-txdata changing 4 files +221 −74
  1. joshdoman commented at 7:58 pm on November 17, 2025: contributor

    This PR exposes a reusable PrecomputedTransactionData object in script validation using libkernel.

    Currently, libkernel computes PrecomputedTransactionData each time btck_script_pubkey_verify is called, exposing clients to quadratic hashing when validating a transaction with multiple inputs. By externalizing PrecomputedTransactionData and making it reusable, libkernel can eliminate this attack vector.

    I discussed this problem in this issue. The design of this PR is inspired by @sedited’s comments.

    The PR introduces three new APIs for managing the btck_PrecomputedTransactionData object:

     0/**
     1 * [@brief](/bitcoin-bitcoin/contributor/brief/) Create precomputed transaction data for script verification.
     2 *
     3 * [@param](/bitcoin-bitcoin/contributor/param/)[in] tx_to             Non-null.
     4 * [@param](/bitcoin-bitcoin/contributor/param/)[in] spent_outputs     Nullable for non-taproot verification. Points to an array of
     5 *                              outputs spent by the transaction.
     6 * [@param](/bitcoin-bitcoin/contributor/param/)[in] spent_outputs_len Length of the spent_outputs array.
     7 * [@return](/bitcoin-bitcoin/contributor/return/)                      The precomputed data, or null on error.
     8 */
     9btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
    10    const btck_Transaction* tx_to,
    11    const btck_TransactionOutput** spent_outputs, size_t spent_outputs_len) BITCOINKERNEL_ARG_NONNULL(1);
    12
    13/**
    14 * [@brief](/bitcoin-bitcoin/contributor/brief/) Copy precomputed transaction data.
    15 *
    16 * [@param](/bitcoin-bitcoin/contributor/param/)[in] precomputed_txdata  Non-null.
    17 * [@return](/bitcoin-bitcoin/contributor/return/)                      The copied precomputed transaction data.
    18 */
    19btck_PrecomputedTransactionData* btck_precomputed_transaction_data_copy(
    20    const btck_PrecomputedTransactionData* precomputed_txdata) BITCOINKERNEL_ARG_NONNULL(1);
    21
    22/**
    23 * Destroy the precomputed transaction data.
    24 */
    25void btck_precomputed_transaction_data_destroy(btck_PrecomputedTransactionData* precomputed_txdata);
    

    The PR also modifies btck_script_pubkey_verify so that it accepts precomputed_txdata instead of spent_outputs:

     0/**
     1 * [@brief](/bitcoin-bitcoin/contributor/brief/) Verify if the input at input_index of tx_to spends the script pubkey
     2 * under the constraints specified by flags. If the
     3 * `btck_ScriptVerificationFlags_WITNESS` flag is set in the flags bitfield, the
     4 * amount parameter is used. If the taproot flag is set, the precomputed data
     5 * must contain the spent outputs.
     6 *
     7 * [@param](/bitcoin-bitcoin/contributor/param/)[in] script_pubkey      Non-null, script pubkey to be spent.
     8 * [@param](/bitcoin-bitcoin/contributor/param/)[in] amount             Amount of the script pubkey's associated output. May be zero if
     9 *                               the witness flag is not set.
    10 * [@param](/bitcoin-bitcoin/contributor/param/)[in] tx_to              Non-null, transaction spending the script_pubkey.
    11 * [@param](/bitcoin-bitcoin/contributor/param/)[in] precomputed_txdata Nullable if the taproot flag is not set. Otherwise, precomputed data
    12 *                               for tx_to with the spent outputs must be provided.
    13 * [@param](/bitcoin-bitcoin/contributor/param/)[in] input_index        Index of the input in tx_to spending the script_pubkey.
    14 * [@param](/bitcoin-bitcoin/contributor/param/)[in] flags              Bitfield of btck_ScriptVerificationFlags controlling validation constraints.
    15 * [@param](/bitcoin-bitcoin/contributor/param/)[out] status            Nullable, will be set to an error code if the operation fails, or OK otherwise.
    16 * [@return](/bitcoin-bitcoin/contributor/return/)                       1 if the script is valid, 0 otherwise.
    17 */
    18int btck_script_pubkey_verify(
    19    const btck_ScriptPubkey* script_pubkey,
    20    int64_t amount,
    21    const btck_Transaction* tx_to,
    22    const btck_PrecomputedTransactionData* precomputed_txdata,
    23    unsigned int input_index,
    24    btck_ScriptVerificationFlags flags,
    25    btck_ScriptVerifyStatus* status) BITCOINKERNEL_ARG_NONNULL(1, 3);
    

    As before, an error is thrown if the taproot flag is set and spent_outputs is not provided in precomputed_txdata (or precomputed_txdata is null). For simple single-input non-taproot verification, precomputed_txdata may be null, and the kernel will construct the precomputed data on-the-fly.

    Both the C++ wrapper and the test suite are updated with the new API. Tests cover both precomputed_txdata reuse and nullability.

    Appreciate feedback on this concept / approach!

  2. DrahtBot commented at 7:58 pm on November 17, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33891.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK sedited, stringintech
    Concept ACK yuvicc, w0xlt
    Stale ACK alexanderwiederin

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33943 (kernel: don’t use assert to handle invalid user input by stickies-v)
    • #33908 (kernel: add context‑free block validation API (btck_check_block_context_free) with POW/Merkle flags by w0xlt)
    • #33822 (kernel: Add block header support and validation by yuvicc)
    • #33796 (kernel: Expose CheckTransaction consensus validation function by w0xlt)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

  3. joshdoman renamed this:
    [kernel] Expose reusable `PrecomputedTransactionData` in script validation
    kernel: Expose reusable `PrecomputedTransactionData` in script validation
    on Nov 17, 2025
  4. DrahtBot added the label Validation on Nov 17, 2025
  5. yuvicc commented at 6:41 am on November 18, 2025: contributor

    Concept ACK

    Wrote a small program to test btck_PrecomputedTransactionData.

  6. w0xlt commented at 0:45 am on November 19, 2025: contributor
    Concept ACK
  7. sedited commented at 10:33 am on November 26, 2025: contributor
    This looks reasonable, Concept ACK
  8. in src/test/kernel/test_kernel.cpp:523 in 0dadeb2e06
    519         /*amount*/ 18393430,
    520         /*input_index*/ 0,
    521         /*is_taproot*/ false);
    522 
    523     // Taproot transaction 33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036
    524+    auto spending_tx{Transaction{hex_string_to_byte_vec("01000000000101d1f1c1f8cdf6759167b90f52c9ad358a369f95284e841d7a2536cef31c0549580100000000fdffffff020000000000000000316a2f49206c696b65205363686e6f7272207369677320616e6420492063616e6e6f74206c69652e204062697462756734329e06010000000000225120a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e5412328f90140a60c383f71bac0ec919b1d7dbc3eb72dd56e7aa99583615564f9f99b8ae4e837b758773a5b2e4c51348854c8389f008e05029db7f464a5ff2e01d5e6e626174affd30a00")}};
    


    sedited commented at 10:54 am on November 27, 2025:
    It would be nice to get a test where we verify both inputs of a two-input taproot transaction with the same data. I think we currently do that implicitly in the full-chain script verification test further down, but I’d like an explicit test too.

    joshdoman commented at 8:57 pm on November 27, 2025:
    An explicit test makes sense. Just added one.
  9. in src/kernel/bitcoinkernel.cpp:619 in 0dadeb2e06
    614+{
    615+    try {
    616+        const CTransaction& tx{*btck_Transaction::get(tx_to)};
    617+        PrecomputedTransactionData txdata{tx};
    618+        std::vector<CTxOut> spent_outputs;
    619+        if (spent_outputs_ != nullptr) {
    


    sedited commented at 10:58 am on November 27, 2025:
    Can you also check that spent_outputs_len is 0 here? It might be the case that the passed in pointer is not null by chance, or UB for that matter, but the intent is still clearly communicated by passing a zero size.

    joshdoman commented at 8:59 pm on November 27, 2025:
    No problem.
  10. in src/kernel/bitcoinkernel.cpp:629 in 0dadeb2e06
    624+                spent_outputs.push_back(tx_out);
    625+            }
    626+            txdata.Init(tx, std::move(spent_outputs));
    627+        }
    628+
    629+        return btck_PrecomputedTransactionData::create(std::move(txdata));
    


    sedited commented at 11:21 am on November 27, 2025:

    I realize you are immitating my own patterns here, but I think it would be better to not rely too heaviliy on the move constructor here, and instead do the following:

     0diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
     1index 4102ee9305..2e86c3d8df 100644
     2--- a/src/kernel/bitcoinkernel.cpp
     3+++ b/src/kernel/bitcoinkernel.cpp
     4@@ -617 +617 @@ btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
     5-        PrecomputedTransactionData txdata{tx};
     6+        auto txdata{btck_PrecomputedTransactionData::create(tx)};
     7@@ -626 +626 @@ btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
     8-            txdata.Init(tx, std::move(spent_outputs));
     9+            btck_PrecomputedTransactionData::get(txdata).Init(tx, std::move(spent_outputs));
    10@@ -629 +629 @@ btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
    11-        return btck_PrecomputedTransactionData::create(std::move(txdata));
    12+        return txdata;
    

    There some other cases in this file where this pattern might be better, but I don’t think it is worth fixing here.


    joshdoman commented at 8:58 pm on November 27, 2025:
    Good suggestion, made that change.
  11. in src/kernel/bitcoinkernel.h:651 in 0dadeb2e06 outdated
    643@@ -606,7 +644,7 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_verify
    644     const btck_ScriptPubkey* script_pubkey,
    645     int64_t amount,
    646     const btck_Transaction* tx_to,
    


    sedited commented at 11:29 am on November 27, 2025:
    The reason I was hesitant to submit this patch earlier is that the developer needs to associate the precomputed data with this transaction, and we don’t have a straight forward semantic mechanism to enforce that. While I don’t think this is particularly problematic, since the same can also be said for the outputs currently, it should be clearly communicated in the documentation for this function and I think also deserves a test case with such a mismatch.

    joshdoman commented at 9:05 pm on November 27, 2025:

    To try to mitigate this concern, I renamed the parameter precomputed_txdata, to make it more clear that it pertains to the actual transaction being verified (previously the parameter was more vaguely named precomputed_data).

    I also updated the parameter description in the documentation for btck_script_pubkey_verify, to make it explicit that the parameter is derived from tx_to and the spent outputs.

    Hopefully these changes help, but open to other suggestions!

  12. in src/test/kernel/test_kernel.cpp:517 in 0dadeb2e06
    513@@ -514,19 +514,21 @@ BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
    514     run_verify_test(
    515         /*spent_script_pubkey*/ ScriptPubkey{hex_string_to_byte_vec("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d")},
    516         /*spending_tx*/ Transaction{hex_string_to_byte_vec("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000")},
    517-        /*spent_outputs*/ {},
    518+        /*precomputed_data*/ nullptr,
    


    sedited commented at 11:39 am on November 27, 2025:
    Can you also add a case for passing in precomputed data for both the segwit and legacy transaction?

    joshdoman commented at 9:05 pm on November 27, 2025:
    Done!
  13. joshdoman force-pushed on Nov 27, 2025
  14. sedited approved
  15. sedited commented at 9:00 pm on November 28, 2025: contributor
    ACK 17d3575cfcb41adfd364e7a1912a4701786f7455
  16. DrahtBot requested review from w0xlt on Nov 28, 2025
  17. in src/kernel/bitcoinkernel.h:574 in 17d3575cfc
    569+ * Functions for working with precomputed transaction data.
    570+ */
    571+///@{
    572+
    573+/**
    574+ * @brief Create precomputed transaction data for efficient script verification.
    


    alexanderwiederin commented at 12:25 pm on December 2, 2025:
    nit: wonder if “efficient” here could be misleading and is necessary.

    joshdoman commented at 3:31 pm on December 18, 2025:

    Hmm, I’m open to removing the word “efficient.” I included it because PrecomputedTransactionData is strictly unnecessary for non-taproot input verification, but it does make verification faster over multiple inputs.

    That may be self-explanatory. If so, we can definitely change the description to Create precomputed transaction data for script verification.


    stringintech commented at 2:25 pm on December 22, 2025:

    I think it is good to mention the efficiency aspect of it, but perhaps it is worth being a bit more clear about it; something like:

     0diff --git a/src/kernel/bitcoinkernel.h b/src/kernel/bitcoinkernel.h
     1index 0947111dba..cf9bde1ab7 100644
     2--- a/src/kernel/bitcoinkernel.h
     3+++ b/src/kernel/bitcoinkernel.h
     4@@ -273,6 +273,9 @@ typedef struct btck_TransactionOutPoint btck_TransactionOutPoint;
     5 
     6 /**
     7  * Opaque data structure for holding precomputed transaction data.
     8+ *
     9+ * This data can be created once and reused when verifying multiple inputs
    10+ * of the same transaction to avoid recomputing transaction hashes for each input.
    11  */
    12 typedef struct btck_PrecomputedTransactionData btck_PrecomputedTransactionData;
    13 
    14@@ -571,7 +574,7 @@ BITCOINKERNEL_API void btck_transaction_destroy(btck_Transaction* transaction);
    15 ///@{
    16 
    17 /**
    18- * [@brief](/bitcoin-bitcoin/contributor/brief/) Create precomputed transaction data for efficient script verification.
    19+ * [@brief](/bitcoin-bitcoin/contributor/brief/) Create precomputed transaction data from a transaction and its spent outputs.
    20  *
    21  * [@param](/bitcoin-bitcoin/contributor/param/)[in] tx_to             Non-null.
    22  * [@param](/bitcoin-bitcoin/contributor/param/)[in] spent_outputs     Nullable for non-taproot verification. Points to an array of
    
  18. DrahtBot added the label CI failed on Dec 15, 2025
  19. DrahtBot removed the label CI failed on Dec 16, 2025
  20. DrahtBot closed this on Dec 16, 2025

  21. DrahtBot reopened this on Dec 16, 2025

  22. DrahtBot added the label CI failed on Dec 16, 2025
  23. DrahtBot removed the label CI failed on Dec 16, 2025
  24. ?
    project_v2_item_status_changed sedited
  25. ?
    added_to_project_v2 sedited
  26. in src/test/kernel/test_kernel.cpp:508 in 17d3575cfc
    501@@ -502,34 +502,95 @@ BOOST_AUTO_TEST_CASE(btck_transaction_input)
    502 BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
    503 {
    504     // Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d
    505+    auto legacy_spent_script_pubkey{ScriptPubkey{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")}};
    506+    auto legacy_spending_tx{Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")}};
    507     run_verify_test(
    508-        /*spent_script_pubkey*/ ScriptPubkey{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")},
    509-        /*spending_tx*/ Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")},
    510+        /*spent_script_pubkey*/ legacy_spent_script_pubkey,
    


    stringintech commented at 1:19 pm on December 21, 2025:

    To make the LLM linter happy:

      0diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
      1index 8faad0667e..d098b08e81 100644
      2--- a/src/test/kernel/test_kernel.cpp
      3+++ b/src/test/kernel/test_kernel.cpp
      4@@ -505,49 +505,49 @@ BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
      5     auto legacy_spent_script_pubkey{ScriptPubkey{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")}};
      6     auto legacy_spending_tx{Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")}};
      7     run_verify_test(
      8-        /*spent_script_pubkey*/ legacy_spent_script_pubkey,
      9-        /*spending_tx*/ legacy_spending_tx,
     10-        /*precomputed_txdata*/ nullptr,
     11-        /*amount*/ 0,
     12-        /*input_index*/ 0,
     13-        /*is_taproot*/ false);
     14+        /*spent_script_pubkey=*/legacy_spent_script_pubkey,
     15+        /*spending_tx=*/legacy_spending_tx,
     16+        /*precomputed_txdata=*/nullptr,
     17+        /*amount=*/0,
     18+        /*input_index=*/0,
     19+        /*taproot=*/false);
     20 
     21     // Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d with precomputed_txdata
     22     auto legacy_precomputed_txdata{PrecomputedTransactionData{
     23-        /*spending_tx*/ legacy_spending_tx,
     24-        /*spent_outputs*/ {},
     25+        /*tx_to=*/legacy_spending_tx,
     26+        /*spent_outputs=*/{},
     27     }};
     28     run_verify_test(
     29-        /*spent_script_pubkey*/ legacy_spent_script_pubkey,
     30-        /*spending_tx*/ legacy_spending_tx,
     31-        /*precomputed_txdata*/ &legacy_precomputed_txdata,
     32-        /*amount*/ 0,
     33-        /*input_index*/ 0,
     34-        /*is_taproot*/ false);
     35+        /*spent_script_pubkey=*/legacy_spent_script_pubkey,
     36+        /*spending_tx=*/legacy_spending_tx,
     37+        /*precomputed_txdata=*/&legacy_precomputed_txdata,
     38+        /*amount=*/0,
     39+        /*input_index=*/0,
     40+        /*taproot=*/false);
     41 
     42     // Segwit transaction 1a3e89644985fbbb41e0dcfe176739813542b5937003c46a07de1e3ee7a4a7f3
     43     auto segwit_spent_script_pubkey{ScriptPubkey{hex_string_to_byte_vec("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d")}};
     44     auto segwit_spending_tx{Transaction{hex_string_to_byte_vec("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000")}};
     45     run_verify_test(
     46-        /*spent_script_pubkey*/ segwit_spent_script_pubkey,
     47-        /*spending_tx*/ segwit_spending_tx,
     48-        /*precomputed_txdata*/ nullptr,
     49-        /*amount*/ 18393430,
     50-        /*input_index*/ 0,
     51-        /*is_taproot*/ false);
     52+        /*spent_script_pubkey=*/segwit_spent_script_pubkey,
     53+        /*spending_tx=*/segwit_spending_tx,
     54+        /*precomputed_txdata=*/nullptr,
     55+        /*amount=*/18393430,
     56+        /*input_index=*/0,
     57+        /*taproot=*/false);
     58 
     59     // Segwit transaction 1a3e89644985fbbb41e0dcfe176739813542b5937003c46a07de1e3ee7a4a7f3 with precomputed_txdata
     60     auto segwit_precomputed_txdata{PrecomputedTransactionData{
     61-        /*spending_tx*/ segwit_spending_tx,
     62-        /*spent_outputs*/ {},
     63+        /*tx_to=*/segwit_spending_tx,
     64+        /*spent_outputs=*/{},
     65     }};
     66     run_verify_test(
     67-        /*spent_script_pubkey*/ segwit_spent_script_pubkey,
     68-        /*spending_tx*/ segwit_spending_tx,
     69-        /*precomputed_txdata*/ &segwit_precomputed_txdata,
     70-        /*amount*/ 18393430,
     71-        /*input_index*/ 0,
     72-        /*is_taproot*/ false);
     73+        /*spent_script_pubkey=*/segwit_spent_script_pubkey,
     74+        /*spending_tx=*/segwit_spending_tx,
     75+        /*precomputed_txdata=*/&segwit_precomputed_txdata,
     76+        /*amount=*/18393430,
     77+        /*input_index=*/0,
     78+        /*taproot=*/false);
     79 
     80     // Taproot transaction 33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036
     81     auto taproot_spent_script_pubkey{ScriptPubkey{hex_string_to_byte_vec("5120339ce7e165e67d93adb3fef88a6d4beed33f01fa876f05a225242b82a631abc0")}};
     82@@ -555,16 +555,16 @@ BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
     83     std::vector<TransactionOutput> taproot_spent_outputs;
     84     taproot_spent_outputs.emplace_back(taproot_spent_script_pubkey, 88480);
     85     auto taproot_precomputed_txdata{PrecomputedTransactionData{
     86-        /*spending_tx*/ taproot_spending_tx,
     87-        /*spent_outputs*/ taproot_spent_outputs,
     88+        /*tx_to=*/taproot_spending_tx,
     89+        /*spent_outputs=*/taproot_spent_outputs,
     90     }};
     91     run_verify_test(
     92-        /*spent_script_pubkey*/ taproot_spent_script_pubkey,
     93-        /*spending_tx*/ taproot_spending_tx,
     94-        /*precomputed_txdata*/ &taproot_precomputed_txdata,
     95-        /*amount*/ 88480,
     96-        /*input_index*/ 0,
     97-        /*is_taproot*/ true);
     98+        /*spent_script_pubkey=*/taproot_spent_script_pubkey,
     99+        /*spending_tx=*/taproot_spending_tx,
    100+        /*precomputed_txdata=*/&taproot_precomputed_txdata,
    101+        /*amount=*/88480,
    102+        /*input_index=*/0,
    103+        /*taproot=*/true);
    104 
    105     // Two-input taproot transaction e8e8320f40c31ed511570e9cdf1d241f8ec9a5cc392e6105240ac8dbea2098de
    106     auto taproot2_spent_script_pubkey0{ScriptPubkey{hex_string_to_byte_vec("5120b7da80f57e36930b0515eb09293e25858d13e6b91fee6184943f5a584cb4248e")}};
    107@@ -574,23 +574,23 @@ BOOST_AUTO_TEST_CASE(btck_script_verify_tests)
    108     taproot2_spent_outputs.emplace_back(taproot2_spent_script_pubkey0, 546);
    109     taproot2_spent_outputs.emplace_back(taproot2_spent_script_pubkey1, 135125);
    110     auto taproot2_precomputed_txdata{PrecomputedTransactionData{
    111-        /*spending_tx*/ taproot2_spending_tx,
    112-        /*spent_outputs*/ taproot2_spent_outputs,
    113+        /*tx_to=*/taproot2_spending_tx,
    114+        /*spent_outputs=*/taproot2_spent_outputs,
    115     }};
    116     run_verify_test(
    117-        /*spent_script_pubkey*/ taproot2_spent_script_pubkey0,
    118-        /*spending_tx*/ taproot2_spending_tx,
    119-        /*precomputed_txdata*/ &taproot2_precomputed_txdata,
    120-        /*amount*/ 546,
    121-        /*input_index*/ 0,
    122-        /*is_taproot*/ true);
    123+        /*spent_script_pubkey=*/taproot2_spent_script_pubkey0,
    124+        /*spending_tx=*/taproot2_spending_tx,
    125+        /*precomputed_txdata=*/&taproot2_precomputed_txdata,
    126+        /*amount=*/546,
    127+        /*input_index=*/0,
    128+        /*taproot=*/true);
    129     run_verify_test(
    130-        /*spent_script_pubkey*/ taproot2_spent_script_pubkey1,
    131-        /*spending_tx*/ taproot2_spending_tx,
    132-        /*precomputed_txdata*/ &taproot2_precomputed_txdata,
    133-        /*amount*/ 135125,
    134-        /*input_index*/ 1,
    135-        /*is_taproot*/ true);
    136+        /*spent_script_pubkey=*/taproot2_spent_script_pubkey1,
    137+        /*spending_tx=*/taproot2_spending_tx,
    138+        /*precomputed_txdata=*/&taproot2_precomputed_txdata,
    139+        /*amount=*/135125,
    140+        /*input_index=*/1,
    141+        /*taproot=*/true);
    142 }
    143 
    144 BOOST_AUTO_TEST_CASE(logging_tests)
    
  27. in src/kernel/bitcoinkernel_wrapper.h:630 in 17d3575cfc outdated
    626@@ -626,29 +627,30 @@ class Transaction : public Handle<btck_Transaction, btck_transaction_copy, btck_
    627         : Handle{view} {}
    628 };
    629 
    630+class PrecomputedTransactionData : public Handle<btck_PrecomputedTransactionData, btck_precomputed_transaction_data_copy, btck_precomputed_transaction_data_destroy>
    


    stringintech commented at 10:44 am on December 22, 2025:

    Since PrecomputedTransactionData is introduced as a Handle, it would be nice to add another unit test to test_kernel.cpp that calls the existing CheckHandle() to increase coverage:

     0diff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp
     1index 8faad0667e..d0fea9ea35 100644
     2--- a/src/test/kernel/test_kernel.cpp
     3+++ b/src/test/kernel/test_kernel.cpp
     4@@ -1036,3 +1036,24 @@ BOOST_AUTO_TEST_CASE(btck_chainman_regtest_tests)
     5     std::filesystem::remove_all(test_directory.m_directory / "blocks" / "rev00000.dat");
     6     BOOST_CHECK_THROW(chainman->ReadBlockSpentOutputs(tip), std::runtime_error);
     7 }
     8+
     9+BOOST_AUTO_TEST_CASE(btck_precomputed_transaction_data_tests)
    10+{
    11+    auto tx_data{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")};
    12+    auto tx{Transaction{tx_data}};
    13+
    14+    auto tx_data_2{hex_string_to_byte_vec("02000000000101904f4ee5c87d20090b642f116e458cd6693292ad9ece23e72f15fb6c05b956210500000000fdffffff02e2010000000000002251200839a723933b56560487ec4d67dda58f09bae518ffa7e148313c5696ac837d9f10060000000000002251205826bcdae7abfb1c468204170eab00d887b61ab143464a4a09e1450bdc59a3340140f26e7af574e647355830772946356c27e7bbc773c5293688890f58983499581be84de40be7311a14e6d6422605df086620e75adae84ff06b75ce5894de5e994a00000000")};
    15+    auto tx2{Transaction{tx_data_2}};
    16+
    17+    auto precomputed_txdata{PrecomputedTransactionData{
    18+        /*tx_to=*/ tx,
    19+        /*spent_outputs=*/ {},
    20+    }};
    21+
    22+    auto precomputed_txdata_2{PrecomputedTransactionData{
    23+        /*tx_to=*/ tx2,
    24+        /*spent_outputs=*/ {},
    25+    }};
    26+
    27+    CheckHandle(precomputed_txdata, precomputed_txdata_2);
    28+}
    

    joshdoman commented at 6:27 pm on December 23, 2025:
    Good call.
  28. in src/kernel/bitcoinkernel.cpp:631 in 17d3575cfc
    624+                spent_outputs.push_back(tx_out);
    625+            }
    626+            btck_PrecomputedTransactionData::get(txdata).Init(tx, std::move(spent_outputs));
    627+        }
    628+
    629+        return txdata;
    


    stringintech commented at 1:46 pm on December 22, 2025:

    Looks like we are triggering PrecomputedTransactionData::Init() twice; first when calling btck_PrecomputedTransactionData::create(tx) by triggering the PrecomputedTransactionData explicit ctor and second when calling it directly. This would cause some recalculations in case of taproot verification which can be avoided by:

     0diff --git a/src/kernel/bitcoinkernel.cpp b/src/kernel/bitcoinkernel.cpp
     1index 13fce99709..5b5dd0aa19 100644
     2--- a/src/kernel/bitcoinkernel.cpp
     3+++ b/src/kernel/bitcoinkernel.cpp
     4@@ -614,16 +614,18 @@ btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
     5 {
     6     try {
     7         const CTransaction& tx{*btck_Transaction::get(tx_to)};
     8-        auto txdata{btck_PrecomputedTransactionData::create(tx)};
     9-        std::vector<CTxOut> spent_outputs;
    10+        auto txdata{btck_PrecomputedTransactionData::create()};
    11         if (spent_outputs_ != nullptr && spent_outputs_len > 0) {
    12             assert(spent_outputs_len == tx.vin.size());
    13+            std::vector<CTxOut> spent_outputs;
    14             spent_outputs.reserve(spent_outputs_len);
    15             for (size_t i = 0; i < spent_outputs_len; i++) {
    16                 const CTxOut& tx_out{btck_TransactionOutput::get(spent_outputs_[i])};
    17                 spent_outputs.push_back(tx_out);
    18             }
    19             btck_PrecomputedTransactionData::get(txdata).Init(tx, std::move(spent_outputs));
    20+        } else {
    21+            btck_PrecomputedTransactionData::get(txdata).Init(tx, {});
    22         }
    23 
    24         return txdata;
    

    sedited commented at 10:43 pm on December 22, 2025:

    Nice catch, looks like it was always done this way since the introduction of taproot support in libbitcoinconsensus: https://github.com/bitcoin/bitcoin/pull/28539/files#diff-12fa382ebec7240468050d4e8d68d88ed12dc55c719ea845664d4373c1e1b494R111-R116

    Your suggested change looks good to me, would eventually be nice to remove this foot-gun.


    joshdoman commented at 6:29 pm on December 23, 2025:
    Agreed, this is a good change.
  29. stringintech commented at 9:56 pm on December 22, 2025: contributor
    Concept ACK
  30. joshdoman force-pushed on Dec 23, 2025
  31. joshdoman force-pushed on Dec 23, 2025
  32. joshdoman force-pushed on Dec 23, 2025
  33. in src/test/kernel/test_kernel.cpp:511 in 1797f8df5b outdated
    524-        /*spent_script_pubkey*/ ScriptPubkey{hex_string_to_byte_vec("76a9144bfbaf6afb76cc5771bc6404810d1cc041a6933988ac")},
    525-        /*spending_tx*/ Transaction{hex_string_to_byte_vec("02000000013f7cebd65c27431a90bba7f796914fe8cc2ddfc3f2cbd6f7e5f2fc854534da95000000006b483045022100de1ac3bcdfb0332207c4a91f3832bd2c2915840165f876ab47c5f8996b971c3602201c6c053d750fadde599e6f5c4e1963df0f01fc0d97815e8157e3d59fe09ca30d012103699b464d1d8bc9e47d4fb1cdaa89a1c5783d68363c4dbc4b524ed3d857148617feffffff02836d3c01000000001976a914fc25d6d5c94003bf5b0c7b640a248e2c637fcfb088ac7ada8202000000001976a914fbed3d9b11183209a57999d54d59f67c019e756c88ac6acb0700")},
    526-        /*spent_outputs*/ {},
    527-        /*amount*/ 0,
    528-        /*input_index*/ 0,
    529-        /*is_taproot*/ false);
    


    sedited commented at 8:44 pm on December 23, 2025:
    This might also be a good opportunity to correct all of these to taproot.
  34. in src/test/kernel/test_kernel.cpp:533 in 1797f8df5b
    534+        /*input_index=*/0,
    535+        /*is_taproot=*/false);
    536+
    537+    // Legacy transaction aca326a724eda9a461c10a876534ecd5ae7b27f10f26c3862fb996f80ea2d45d with precomputed_txdata
    538+    auto legacy_precomputed_txdata{PrecomputedTransactionData{
    539+        /*spending_tx=*/legacy_spending_tx,
    


    sedited commented at 8:46 pm on December 23, 2025:
    This should be tx_to (and similarly for the other PrecomputedTransactionData constructors.
  35. joshdoman force-pushed on Dec 23, 2025
  36. DrahtBot added the label CI failed on Dec 23, 2025
  37. DrahtBot commented at 10:45 pm on December 23, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/actions/runs/20471517120/job/58829202332 LLM reason (✨ experimental): Trailing newline lint failure (trailing_newline check) caused the CI to fail.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  38. [kernel] Expose reusable PrecomputedTransactionData in script valid 44e006d438
  39. joshdoman force-pushed on Dec 23, 2025
  40. sedited approved
  41. sedited commented at 9:45 am on December 24, 2025: contributor
    Re-ACK 44e006d4383155f254f908ada91c2d9a7a65db6c
  42. DrahtBot requested review from alexanderwiederin on Dec 24, 2025
  43. DrahtBot requested review from stringintech on Dec 24, 2025
  44. stringintech commented at 9:57 am on December 24, 2025: contributor
    ACK 44e006d
  45. DrahtBot removed the label CI failed on Dec 26, 2025
  46. fanquake merged this on Dec 27, 2025
  47. fanquake closed this on Dec 27, 2025

  48. ?
    project_v2_item_status_changed github-project-automation[bot]
  49. sedited referenced this in commit f8db928648 on Dec 27, 2025
  50. joshdoman referenced this in commit 57661ceac9 on Dec 27, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-01-10 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me