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:
/**
* [@brief](/bitcoin-bitcoin/contributor/brief/) Create precomputed transaction data for script verification.
*
* [@param](/bitcoin-bitcoin/contributor/param/)[in] tx_to Non-null.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] spent_outputs Nullable for non-taproot verification. Points to an array of
* outputs spent by the transaction.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] spent_outputs_len Length of the spent_outputs array.
* [@return](/bitcoin-bitcoin/contributor/return/) The precomputed data, or null on error.
*/
btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
const btck_Transaction* tx_to,
const btck_TransactionOutput** spent_outputs, size_t spent_outputs_len) BITCOINKERNEL_ARG_NONNULL(1);
/**
* [@brief](/bitcoin-bitcoin/contributor/brief/) Copy precomputed transaction data.
*
* [@param](/bitcoin-bitcoin/contributor/param/)[in] precomputed_txdata Non-null.
* [@return](/bitcoin-bitcoin/contributor/return/) The copied precomputed transaction data.
*/
btck_PrecomputedTransactionData* btck_precomputed_transaction_data_copy(
const btck_PrecomputedTransactionData* precomputed_txdata) BITCOINKERNEL_ARG_NONNULL(1);
/**
* Destroy the precomputed transaction data.
*/
void 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:
/**
* [@brief](/bitcoin-bitcoin/contributor/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 precomputed data
* must contain the spent outputs.
*
* [@param](/bitcoin-bitcoin/contributor/param/)[in] script_pubkey Non-null, script pubkey to be spent.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] amount Amount of the script pubkey's associated output. May be zero if
* the witness flag is not set.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] tx_to Non-null, transaction spending the script_pubkey.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] precomputed_txdata Nullable if the taproot flag is not set. Otherwise, precomputed data
* for tx_to with the spent outputs must be provided.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] input_index Index of the input in tx_to spending the script_pubkey.
* [@param](/bitcoin-bitcoin/contributor/param/)[in] flags Bitfield of btck_ScriptVerificationFlags controlling validation constraints.
* [@param](/bitcoin-bitcoin/contributor/param/)[out] status Nullable, will be set to an error code if the operation fails, or OK otherwise.
* [@return](/bitcoin-bitcoin/contributor/return/) 1 if the script is valid, 0 otherwise.
*/
int btck_script_pubkey_verify(
const btck_ScriptPubkey* script_pubkey,
int64_t amount,
const btck_Transaction* tx_to,
const btck_PrecomputedTransactionData* precomputed_txdata,
unsigned int input_index,
btck_ScriptVerificationFlags flags,
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!