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 efficient 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!