mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-29 09:55:49 +02:00
Merge bitcoin/bitcoin#33891: kernel: Expose reusable PrecomputedTransactionData in script validation
44e006d438[kernel] Expose reusable PrecomputedTransactionData in script valid (Josh Doman) Pull request description: 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](https://github.com/TheCharlatan/rust-bitcoinkernel/issues/46). The design of this PR is inspired by @sedited's comments. The PR introduces three new APIs for managing the `btck_PrecomputedTransactionData` object: ```c /** * @brief Create precomputed transaction data for script verification. * * @param[in] tx_to Non-null. * @param[in] spent_outputs Nullable for non-taproot verification. Points to an array of * outputs spent by the transaction. * @param[in] spent_outputs_len Length of the spent_outputs array. * @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 Copy precomputed transaction data. * * @param[in] precomputed_txdata Non-null. * @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`: ```c /** * @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[in] script_pubkey Non-null, script pubkey to be spent. * @param[in] amount Amount of the script pubkey's associated output. May be zero if * the witness flag is not set. * @param[in] tx_to Non-null, transaction spending the script_pubkey. * @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[in] input_index Index of the input in tx_to spending the script_pubkey. * @param[in] flags Bitfield of btck_ScriptVerificationFlags controlling validation constraints. * @param[out] status Nullable, will be set to an error code if the operation fails, or OK otherwise. * @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! ACKs for top commit: sedited: Re-ACK44e006d438stringintech: ACK44e006dTree-SHA512: 1ed435173e6ff4ec82bc603194cf182c685cb79f167439a442b9b179a32f6c189c358f04d4cb56d153fab04e3424a11b73c31680e42b87b8a6efcc3ccefc366c
This commit is contained in:
@@ -495,6 +495,7 @@ struct btck_BlockHash : Handle<btck_BlockHash, uint256> {};
|
||||
struct btck_TransactionInput : Handle<btck_TransactionInput, CTxIn> {};
|
||||
struct btck_TransactionOutPoint: Handle<btck_TransactionOutPoint, COutPoint> {};
|
||||
struct btck_Txid: Handle<btck_Txid, Txid> {};
|
||||
struct btck_PrecomputedTransactionData : Handle<btck_PrecomputedTransactionData, PrecomputedTransactionData> {};
|
||||
|
||||
btck_Transaction* btck_transaction_create(const void* raw_transaction, size_t raw_transaction_len)
|
||||
{
|
||||
@@ -608,10 +609,46 @@ void btck_transaction_output_destroy(btck_TransactionOutput* output)
|
||||
delete output;
|
||||
}
|
||||
|
||||
btck_PrecomputedTransactionData* btck_precomputed_transaction_data_create(
|
||||
const btck_Transaction* tx_to,
|
||||
const btck_TransactionOutput** spent_outputs_, size_t spent_outputs_len)
|
||||
{
|
||||
try {
|
||||
const CTransaction& tx{*btck_Transaction::get(tx_to)};
|
||||
auto txdata{btck_PrecomputedTransactionData::create()};
|
||||
if (spent_outputs_ != nullptr && spent_outputs_len > 0) {
|
||||
assert(spent_outputs_len == tx.vin.size());
|
||||
std::vector<CTxOut> spent_outputs;
|
||||
spent_outputs.reserve(spent_outputs_len);
|
||||
for (size_t i = 0; i < spent_outputs_len; i++) {
|
||||
const CTxOut& tx_out{btck_TransactionOutput::get(spent_outputs_[i])};
|
||||
spent_outputs.push_back(tx_out);
|
||||
}
|
||||
btck_PrecomputedTransactionData::get(txdata).Init(tx, std::move(spent_outputs));
|
||||
} else {
|
||||
btck_PrecomputedTransactionData::get(txdata).Init(tx, {});
|
||||
}
|
||||
|
||||
return txdata;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
btck_PrecomputedTransactionData* btck_precomputed_transaction_data_copy(const btck_PrecomputedTransactionData* precomputed_txdata)
|
||||
{
|
||||
return btck_PrecomputedTransactionData::copy(precomputed_txdata);
|
||||
}
|
||||
|
||||
void btck_precomputed_transaction_data_destroy(btck_PrecomputedTransactionData* precomputed_txdata)
|
||||
{
|
||||
delete precomputed_txdata;
|
||||
}
|
||||
|
||||
int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,
|
||||
const int64_t amount,
|
||||
const btck_Transaction* tx_to,
|
||||
const btck_TransactionOutput** spent_outputs_, size_t spent_outputs_len,
|
||||
const btck_PrecomputedTransactionData* precomputed_txdata,
|
||||
const unsigned int input_index,
|
||||
const btck_ScriptVerificationFlags flags,
|
||||
btck_ScriptVerifyStatus* status)
|
||||
@@ -624,31 +661,18 @@ int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (flags & btck_ScriptVerificationFlags_TAPROOT && spent_outputs_ == nullptr) {
|
||||
const CTransaction& tx{*btck_Transaction::get(tx_to)};
|
||||
assert(input_index < tx.vin.size());
|
||||
|
||||
const PrecomputedTransactionData& txdata{precomputed_txdata ? btck_PrecomputedTransactionData::get(precomputed_txdata) : PrecomputedTransactionData(tx)};
|
||||
|
||||
if (flags & btck_ScriptVerificationFlags_TAPROOT && txdata.m_spent_outputs.empty()) {
|
||||
if (status) *status = btck_ScriptVerifyStatus_ERROR_SPENT_OUTPUTS_REQUIRED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (status) *status = btck_ScriptVerifyStatus_OK;
|
||||
|
||||
const CTransaction& tx{*btck_Transaction::get(tx_to)};
|
||||
std::vector<CTxOut> spent_outputs;
|
||||
if (spent_outputs_ != nullptr) {
|
||||
assert(spent_outputs_len == tx.vin.size());
|
||||
spent_outputs.reserve(spent_outputs_len);
|
||||
for (size_t i = 0; i < spent_outputs_len; i++) {
|
||||
const CTxOut& tx_out{btck_TransactionOutput::get(spent_outputs_[i])};
|
||||
spent_outputs.push_back(tx_out);
|
||||
}
|
||||
}
|
||||
|
||||
assert(input_index < tx.vin.size());
|
||||
PrecomputedTransactionData txdata{tx};
|
||||
|
||||
if (spent_outputs_ != nullptr && flags & btck_ScriptVerificationFlags_TAPROOT) {
|
||||
txdata.Init(tx, std::move(spent_outputs));
|
||||
}
|
||||
|
||||
bool result = VerifyScript(tx.vin[input_index].scriptSig,
|
||||
btck_ScriptPubkey::get(script_pubkey),
|
||||
&tx.vin[input_index].scriptWitness,
|
||||
|
||||
Reference in New Issue
Block a user