Merge bitcoin/bitcoin#31622: psbt: add non-default sighash types to PSBTs and unify sighash type match checking

ee045b61ef rpc, psbt: Require sighashes match for descriptorprocesspsbt (Ava Chow)
2b7682c372 psbt: use sighash type field to determine whether to remove non-witness utxos (Ava Chow)
28781b5f06 psbt: Add sighash types to PSBT when not DEFAULT or ALL (Ava Chow)
15ce1bd73f psbt: Enforce sighash type of signatures matches psbt (Ava Chow)
1f71cd337a wallet: Remove sighash type enforcement from FillPSBT (Ava Chow)
4c7d767e49 psbt: Check sighash types in SignPSBTInput and take sighash as optional (Ava Chow)
a118256948 script: Add IsPayToTaproot() (Ava Chow)
d6001dcd4a wallet: change FillPSBT to take sighash as optional (Ava Chow)
e58b680923 psbt: Return PSBTError from SignPSBTInput (Ava Chow)
2adfd81532 tests: Test PSBT sighash type mismatch (Ava Chow)
5a5d26d612 psbt: Require ECDSA signatures to be validly encoded (Ava Chow)

Pull request description:

  Currently, we do not add the sighash field to PSBTs at all, even when we have signed with a non-default sighash. This PR changes the behavior such that when we (attempt to) sign with a sighash other than DEFAULT or ALL, the sighash type field will be added to the PSBT to inform the later signers that a different sighash type was used by a signer. Notably, this is necessary for MuSig2 support as all signers must sign using the same sighash type, but the sighash is not provided in partial signatures.

  Furthermore, because the sighash type can also be provided on the command line, we require that if both a command line sighash type and the sighash field is present, they must specify the same sighash type. However, this was being checked by the wallet, rather than the signing code, so the `descriptorprocesspsbt` RPC was not enforcing this restriction at all, and in fact ignored the sighash field entirely. This PR refactors the checking code so that the underlying PSBT signing function `SignPSBTInput` does the check.

ACKs for top commit:
  theStack:
    re-ACK ee045b61ef
  rkrux:
    re-ACK ee045b61ef
  fjahr:
    Code review ACK ee045b61ef

Tree-SHA512: 4ead5be1ef6756251b827f594beba868a145d75bf7f4ef6f15ad21f0ae4b8d71b38c83494e5a6b75f37fadd097178cddd93d614b962a2c72fc134f00ba2f74ae
This commit is contained in:
merge-script committed 2025-05-21 10:02:49 +01:00
commit ec81204694
27 files changed
+253 -84

No files matched your search

+4
View File
@@ -116,6 +116,10 @@ bilingual_str PSBTErrorString(PSBTError err)
return Untranslated("External signer failed to sign"); return Untranslated("External signer failed to sign");
case PSBTError::UNSUPPORTED: case PSBTError::UNSUPPORTED:
return Untranslated("Signer does not support PSBT"); return Untranslated("Signer does not support PSBT");
case PSBTError::INCOMPLETE:
return Untranslated("Input needs additional signatures or other data");
case PSBTError::OK:
return Untranslated("No errors");
// no default case, so the compiler can warn about missing cases // no default case, so the compiler can warn about missing cases
} }
assert(false); assert(false);
+2
View File
@@ -20,6 +20,8 @@ enum class PSBTError {
EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_NOT_FOUND,
EXTERNAL_SIGNER_FAILED, EXTERNAL_SIGNER_FAILED,
UNSUPPORTED, UNSUPPORTED,
INCOMPLETE,
OK,
}; };
} // namespace common } // namespace common
+1 -1
View File
@@ -205,7 +205,7 @@ public:
int& num_blocks) = 0; int& num_blocks) = 0;
//! Fill PSBT. //! Fill PSBT.
virtual std::optional<common::PSBTError> fillPSBT(int sighash_type, virtual std::optional<common::PSBTError> fillPSBT(std::optional<int> sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,
+2 -2
View File
@@ -64,7 +64,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Figure out what is missing // Figure out what is missing
SignatureData outdata; SignatureData outdata;
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, std::nullopt, &outdata) == PSBTError::OK;
// Things are missing // Things are missing
if (!complete) { if (!complete) {
@@ -124,7 +124,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i]; PSBTInput& input = psbtx.inputs[i];
Coin newcoin; Coin newcoin;
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { if (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, std::nullopt) != PSBTError::OK || !psbtx.GetInputUTXO(newcoin.out, i)) {
success = false; success = false;
break; break;
} else { } else {
+82 -34
View File
@@ -4,12 +4,15 @@
#include <psbt.h> #include <psbt.h>
#include <common/types.h>
#include <node/types.h> #include <node/types.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <script/signingprovider.h> #include <script/signingprovider.h>
#include <util/check.h> #include <util/check.h>
#include <util/strencodings.h> #include <util/strencodings.h>
using common::PSBTError;
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{ {
inputs.resize(tx.vin.size()); inputs.resize(tx.vin.size());
@@ -372,13 +375,13 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
return txdata; return txdata;
} }
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize) PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, std::optional<int> sighash, SignatureData* out_sigdata, bool finalize)
{ {
PSBTInput& input = psbt.inputs.at(index); PSBTInput& input = psbt.inputs.at(index);
const CMutableTransaction& tx = *psbt.tx; const CMutableTransaction& tx = *psbt.tx;
if (PSBTInputSignedAndVerified(psbt, index, txdata)) { if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
return true; return PSBTError::OK;
} }
// Fill SignatureData with input info // Fill SignatureData with input info
@@ -393,10 +396,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout. // If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
COutPoint prevout = tx.vin[index].prevout; COutPoint prevout = tx.vin[index].prevout;
if (prevout.n >= input.non_witness_utxo->vout.size()) { if (prevout.n >= input.non_witness_utxo->vout.size()) {
return false; return PSBTError::MISSING_INPUTS;
} }
if (input.non_witness_utxo->GetHash() != prevout.hash) { if (input.non_witness_utxo->GetHash() != prevout.hash) {
return false; return PSBTError::MISSING_INPUTS;
} }
utxo = input.non_witness_utxo->vout[prevout.n]; utxo = input.non_witness_utxo->vout[prevout.n];
} else if (!input.witness_utxo.IsNull()) { } else if (!input.witness_utxo.IsNull()) {
@@ -407,7 +410,46 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
// a witness signature in this situation. // a witness signature in this situation.
require_witness_sig = true; require_witness_sig = true;
} else { } else {
return false; return PSBTError::MISSING_INPUTS;
}
// Get the sighash type
// If both the field and the parameter are provided, they must match
// If only the parameter is provided, use it and add it to the PSBT if it is other than SIGHASH_DEFAULT
// for all input types, and not SIGHASH_ALL for non-taproot input types.
// If neither are provided, use SIGHASH_DEFAULT if it is taproot, and SIGHASH_ALL for everything else.
if (!sighash) sighash = utxo.scriptPubKey.IsPayToTaproot() ? SIGHASH_DEFAULT : SIGHASH_ALL;
Assert(sighash.has_value());
// For user safety, the desired sighash must be provided if the PSBT wants something other than the default set in the previous line.
if (input.sighash_type && input.sighash_type != sighash) {
return PSBTError::SIGHASH_MISMATCH;
}
// Set the PSBT sighash field when sighash is not DEFAULT or ALL
// DEFAULT is allowed for non-taproot inputs since DEFAULT may be passed for them (e.g. the psbt being signed also has taproot inputs)
// Note that signing already aliases DEFAULT to ALL for non-taproot inputs.
if (utxo.scriptPubKey.IsPayToTaproot() ? sighash != SIGHASH_DEFAULT :
(sighash != SIGHASH_DEFAULT && sighash != SIGHASH_ALL)) {
input.sighash_type = sighash;
}
// Check all existing signatures use the sighash type
if (sighash == SIGHASH_DEFAULT) {
if (!input.m_tap_key_sig.empty() && input.m_tap_key_sig.size() != 64) {
return PSBTError::SIGHASH_MISMATCH;
}
for (const auto& [_, sig] : input.m_tap_script_sigs) {
if (sig.size() != 64) return PSBTError::SIGHASH_MISMATCH;
}
} else {
if (!input.m_tap_key_sig.empty() && (input.m_tap_key_sig.size() != 65 || input.m_tap_key_sig.back() != *sighash)) {
return PSBTError::SIGHASH_MISMATCH;
}
for (const auto& [_, sig] : input.m_tap_script_sigs) {
if (sig.size() != 65 || sig.back() != *sighash) return PSBTError::SIGHASH_MISMATCH;
}
for (const auto& [_, sig] : input.partial_sigs) {
if (sig.second.back() != *sighash) return PSBTError::SIGHASH_MISMATCH;
}
} }
sigdata.witness = false; sigdata.witness = false;
@@ -415,11 +457,11 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
if (txdata == nullptr) { if (txdata == nullptr) {
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
} else { } else {
MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, sighash); MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, *sighash);
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
} }
// Verify that a witness signature was produced in case one was required. // Verify that a witness signature was produced in case one was required.
if (require_witness_sig && !sigdata.witness) return false; if (require_witness_sig && !sigdata.witness) return PSBTError::INCOMPLETE;
// If we are not finalizing, set sigdata.complete to false to not set the scriptWitness // If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
if (!finalize && sigdata.complete) sigdata.complete = false; if (!finalize && sigdata.complete) sigdata.complete = false;
@@ -442,39 +484,44 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
out_sigdata->missing_witness_script = sigdata.missing_witness_script; out_sigdata->missing_witness_script = sigdata.missing_witness_script;
} }
return sig_complete; return sig_complete ? PSBTError::OK : PSBTError::INCOMPLETE;
} }
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type) void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx)
{ {
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY // Figure out if any non_witness_utxos should be dropped
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) { std::vector<unsigned int> to_drop;
// Figure out if any non_witness_utxos should be dropped for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
std::vector<unsigned int> to_drop; const auto& input = psbtx.inputs.at(i);
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { int wit_ver;
const auto& input = psbtx.inputs.at(i); std::vector<unsigned char> wit_prog;
int wit_ver; if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
std::vector<unsigned char> wit_prog; // There's a non-segwit input, so we cannot drop any non_witness_utxos
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) { to_drop.clear();
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos break;
to_drop.clear(); }
break; if (wit_ver == 0) {
} // Segwit v0, so we cannot drop any non_witness_utxos
if (wit_ver == 0) { to_drop.clear();
// Segwit v0, so we cannot drop any non_witness_utxos break;
to_drop.clear(); }
break; // non_witness_utxos cannot be dropped if the sighash type includes SIGHASH_ANYONECANPAY
} // Since callers should have called SignPSBTInput which updates the sighash type in the PSBT, we only
if (input.non_witness_utxo) { // need to look at that field. If it is not present, then we can assume SIGHASH_DEFAULT or SIGHASH_ALL.
to_drop.push_back(i); if (input.sighash_type != std::nullopt && (*input.sighash_type & 0x80) == SIGHASH_ANYONECANPAY) {
} to_drop.clear();
break;
} }
// Drop the non_witness_utxos that we can drop if (input.non_witness_utxo) {
for (unsigned int i : to_drop) { to_drop.push_back(i);
psbtx.inputs.at(i).non_witness_utxo = nullptr;
} }
} }
// Drop the non_witness_utxos that we can drop
for (unsigned int i : to_drop) {
psbtx.inputs.at(i).non_witness_utxo = nullptr;
}
} }
bool FinalizePSBT(PartiallySignedTransaction& psbtx) bool FinalizePSBT(PartiallySignedTransaction& psbtx)
@@ -486,7 +533,8 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
bool complete = true; bool complete = true;
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true); PSBTInput& input = psbtx.inputs.at(i);
complete &= (SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, input.sighash_type, nullptr, true) == PSBTError::OK);
} }
return complete; return complete;
+10 -2
View File
@@ -5,6 +5,7 @@
#ifndef BITCOIN_PSBT_H #ifndef BITCOIN_PSBT_H
#define BITCOIN_PSBT_H #define BITCOIN_PSBT_H
#include <common/types.h>
#include <node/transaction.h> #include <node/transaction.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
@@ -21,6 +22,8 @@ namespace node {
enum class TransactionError; enum class TransactionError;
} // namespace node } // namespace node
using common::PSBTError;
// Magic bytes // Magic bytes
static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
@@ -520,6 +523,11 @@ struct PSBTInput
std::vector<unsigned char> sig; std::vector<unsigned char> sig;
s >> sig; s >> sig;
// Check that the signature is validly encoded
if (sig.empty() || !CheckSignatureEncoding(sig, SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_STRICTENC, nullptr)) {
throw std::ios_base::failure("Signature is not a valid encoding");
}
// Add to list // Add to list
partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig))); partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig)));
break; break;
@@ -1396,10 +1404,10 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
* txdata should be the output of PrecomputePSBTData (which can be shared across * txdata should be the output of PrecomputePSBTData (which can be shared across
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
**/ **/
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true); [[nodiscard]] PSBTError SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, std::optional<int> sighash = std::nullopt, SignatureData* out_sigdata = nullptr, bool finalize = true);
/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */ /** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type); void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx);
/** Counts the unsigned inputs of a PSBT. */ /** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
+3 -3
View File
@@ -59,7 +59,7 @@ void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)
bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness. bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.
if (m_wallet_model) { if (m_wallet_model) {
size_t n_could_sign; size_t n_could_sign;
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, &n_could_sign, m_transaction_data, complete)};
if (err) { if (err) {
showStatus(tr("Failed to load transaction: %1") showStatus(tr("Failed to load transaction: %1")
.arg(QString::fromStdString(PSBTErrorString(*err).translated)), .arg(QString::fromStdString(PSBTErrorString(*err).translated)),
@@ -83,7 +83,7 @@ void PSBTOperationsDialog::signTransaction()
WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock()); WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock());
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, &n_signed, m_transaction_data, complete)};
if (err) { if (err) {
showStatus(tr("Failed to sign transaction: %1") showStatus(tr("Failed to sign transaction: %1")
@@ -251,7 +251,7 @@ size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &p
size_t n_signed; size_t n_signed;
bool complete; bool complete;
const auto err{m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)}; const auto err{m_wallet_model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/false, &n_signed, m_transaction_data, complete)};
if (err) { if (err) {
return 0; return 0;
+3 -3
View File
@@ -450,7 +450,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) { bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
std::optional<PSBTError> err; std::optional<PSBTError> err;
try { try {
err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete); err = model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
} catch (const std::runtime_error& e) { } catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Sign failed"), e.what()); QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
return false; return false;
@@ -507,7 +507,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
// Fill without signing // Fill without signing
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)}; const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(!err); assert(!err);
@@ -523,7 +523,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool complete = false; bool complete = false;
// Always fill without signing first. This prevents an external signer // Always fill without signing first. This prevents an external signer
// from being called prematurely and is not expensive. // from being called prematurely and is not expensive.
const auto err{model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)}; const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
assert(!complete); assert(!complete);
assert(!err); assert(!err);
send_failure = !signWithExternalSigner(psbtx, mtx, complete); send_failure = !signWithExternalSigner(psbtx, mtx, complete);
+1 -1
View File
@@ -521,7 +521,7 @@ bool WalletModel::bumpFee(Txid hash, Txid& new_hash)
// "Create Unsigned" clicked // "Create Unsigned" clicked
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const auto err{wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)}; const auto err{wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)};
if (err || complete) { if (err || complete) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction.")); QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
return false; return false;
+8 -5
View File
@@ -163,7 +163,7 @@ static std::vector<RPCArg> CreateTxDoc()
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors. // Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
// Optionally, sign the inputs that we can using information from the descriptors. // Optionally, sign the inputs that we can using information from the descriptors.
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize) PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, std::optional<int> sighash_type, bool finalize)
{ {
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
@@ -235,7 +235,10 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
// We only actually care about those if our signing provider doesn't hide private // We only actually care about those if our signing provider doesn't hide private
// information, as is the case with `descriptorprocesspsbt` // information, as is the case with `descriptorprocesspsbt`
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize); // Only error for mismatching sighash types as it is critical that the sighash to sign with matches the PSBT's
if (SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize) == common::PSBTError::SIGHASH_MISMATCH) {
throw JSONRPCPSBTError(common::PSBTError::SIGHASH_MISMATCH);
}
} }
// Update script/keypath information using descriptor data. // Update script/keypath information using descriptor data.
@@ -243,7 +246,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
UpdatePSBTOutput(provider, psbtx, i); UpdatePSBTOutput(provider, psbtx, i);
} }
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1); RemoveUnnecessaryTransactions(psbtx);
return psbtx; return psbtx;
} }
@@ -1802,7 +1805,7 @@ static RPCHelpMan utxoupdatepsbt()
request.params[0].get_str(), request.params[0].get_str(),
request.context, request.context,
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false), HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
/*sighash_type=*/SIGHASH_ALL, /*sighash_type=*/std::nullopt,
/*finalize=*/false); /*finalize=*/false);
DataStream ssTx{}; DataStream ssTx{};
@@ -2072,7 +2075,7 @@ RPCHelpMan descriptorprocesspsbt()
EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true); EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
} }
int sighash_type = ParseSighashString(request.params[2]); std::optional<int> sighash_type = ParseSighashString(request.params[2]);
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool(); bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
+5 -2
View File
@@ -304,12 +304,15 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FlatSigningProvider* keystore,
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result) void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result)
{ {
int nHashType = ParseSighashString(hashType); std::optional<int> nHashType = ParseSighashString(hashType);
if (!nHashType) {
nHashType = SIGHASH_DEFAULT;
}
// Script verification errors // Script verification errors
std::map<int, bilingual_str> input_errors; std::map<int, bilingual_str> input_errors;
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors); bool complete = SignTransaction(mtx, keystore, coins, *nHashType, input_errors);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
} }
+2 -2
View File
@@ -357,10 +357,10 @@ UniValue DescribeAddress(const CTxDestination& dest)
* *
* @pre The sighash argument should be string or null. * @pre The sighash argument should be string or null.
*/ */
int ParseSighashString(const UniValue& sighash) std::optional<int> ParseSighashString(const UniValue& sighash)
{ {
if (sighash.isNull()) { if (sighash.isNull()) {
return SIGHASH_DEFAULT; return std::nullopt;
} }
const auto result{SighashFromStr(sighash.get_str())}; const auto result{SighashFromStr(sighash.get_str())};
if (!result) { if (!result) {
+1 -1
View File
@@ -137,7 +137,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
UniValue DescribeAddress(const CTxDestination& dest); UniValue DescribeAddress(const CTxDestination& dest);
/** Parse a sighash string representation and raise an RPC error if it is invalid. */ /** Parse a sighash string representation and raise an RPC error if it is invalid. */
int ParseSighashString(const UniValue& sighash); std::optional<int> ParseSighashString(const UniValue& sighash);
//! Parse a confirm target option and raise an RPC error if it is invalid. //! Parse a confirm target option and raise an RPC error if it is invalid.
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target); unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);
+7
View File
@@ -238,6 +238,13 @@ bool CScript::IsPayToWitnessScriptHash() const
(*this)[1] == 0x20); (*this)[1] == 0x20);
} }
bool CScript::IsPayToTaproot() const
{
return (this->size() == 34 &&
(*this)[0] == OP_1 &&
(*this)[1] == 0x20);
}
// A witness program is any valid CScript that consists of a 1-byte push opcode // A witness program is any valid CScript that consists of a 1-byte push opcode
// followed by a data push between 2 and 40 bytes. // followed by a data push between 2 and 40 bytes.
bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const
+2
View File
@@ -556,6 +556,8 @@ public:
bool IsPayToWitnessScriptHash() const; bool IsPayToWitnessScriptHash() const;
bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const; bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;
bool IsPayToTaproot() const;
/** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */
bool IsPushOnly(const_iterator pc) const; bool IsPushOnly(const_iterator pc) const;
bool IsPushOnly() const; bool IsPushOnly() const;
@@ -79,7 +79,7 @@ util::Result<void> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestin
} }
// If sign is true, transaction must previously have been filled // If sign is true, transaction must previously have been filled
std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (!sign) { if (!sign) {
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize); return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize);
+1 -1
View File
@@ -35,7 +35,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
*/ */
util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const; util::Result<void> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
}; };
} // namespace wallet } // namespace wallet
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
+2 -2
View File
@@ -338,8 +338,8 @@ bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
// First fill transaction with our data without signing, // First fill transaction with our data without signing,
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); wallet.FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true);
auto err{wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */)}; auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, /*sign=*/true, /*bip32derivs=*/false)};
if (err) return false; if (err) return false;
complete = FinalizeAndExtractPSBT(psbtx, mtx); complete = FinalizeAndExtractPSBT(psbtx, mtx);
return complete; return complete;
+1 -1
View File
@@ -383,7 +383,7 @@ public:
} }
return {}; return {};
} }
std::optional<PSBTError> fillPSBT(int sighash_type, std::optional<PSBTError> fillPSBT(std::optional<int> sighash_type,
bool sign, bool sign,
bool bip32derivs, bool bip32derivs,
size_t* n_signed, size_t* n_signed,
+10 -7
View File
@@ -100,8 +100,8 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
// First fill transaction with our data without signing, // First fill transaction with our data without signing,
// so external signers are not asked to sign more than once. // so external signers are not asked to sign more than once.
bool complete; bool complete;
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true); pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true);
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)}; const auto err{pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/true, /*bip32derivs=*/false)};
if (err) { if (err) {
throw JSONRPCPSBTError(*err); throw JSONRPCPSBTError(*err);
} }
@@ -960,12 +960,15 @@ RPCHelpMan signrawtransactionwithwallet()
// Parse the prevtxs array // Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins); ParsePrevouts(request.params[1], nullptr, coins);
int nHashType = ParseSighashString(request.params[2]); std::optional<int> nHashType = ParseSighashString(request.params[2]);
if (!nHashType) {
nHashType = SIGHASH_DEFAULT;
}
// Script verification errors // Script verification errors
std::map<int, bilingual_str> input_errors; std::map<int, bilingual_str> input_errors;
bool complete = pwallet->SignTransaction(mtx, coins, nHashType, input_errors); bool complete = pwallet->SignTransaction(mtx, coins, *nHashType, input_errors);
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result); SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
return result; return result;
@@ -1179,7 +1182,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
} else { } else {
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true)}; const auto err{pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true)};
CHECK_NONFATAL(!err); CHECK_NONFATAL(!err);
CHECK_NONFATAL(!complete); CHECK_NONFATAL(!complete);
DataStream ssTx{}; DataStream ssTx{};
@@ -1635,7 +1638,7 @@ RPCHelpMan walletprocesspsbt()
} }
// Get the sighash type // Get the sighash type
int nHashType = ParseSighashString(request.params[2]); std::optional<int> nHashType = ParseSighashString(request.params[2]);
// Fill transaction with our data and also sign // Fill transaction with our data and also sign
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
@@ -1784,7 +1787,7 @@ RPCHelpMan walletcreatefundedpsbt()
// Fill transaction with out data but don't sign // Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true; bool complete = true;
const auto err{wallet.FillPSBT(psbtx, complete, 1, /*sign=*/false, /*bip32derivs=*/bip32derivs)}; const auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/bip32derivs)};
if (err) { if (err) {
throw JSONRPCPSBTError(*err); throw JSONRPCPSBTError(*err);
} }
+5 -7
View File
@@ -1311,7 +1311,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
return SigningResult::OK; return SigningResult::OK;
} }
std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@@ -1324,11 +1324,6 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
continue; continue;
} }
// Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return PSBTError::SIGHASH_MISMATCH;
}
// Get the scriptPubKey to know which SigningProvider to use // Get the scriptPubKey to know which SigningProvider to use
CScript script; CScript script;
if (!input.witness_utxo.IsNull()) { if (!input.witness_utxo.IsNull()) {
@@ -1386,7 +1381,10 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
} }
} }
SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); PSBTError res = SignPSBTInput(HidingSigningProvider(keys.get(), /*hide_secret=*/!sign, /*hide_origin=*/!bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
if (res != PSBTError::OK && res != PSBTError::INCOMPLETE) {
return res;
}
bool signed_one = PSBTInputSigned(input); bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) { if (n_signed && (signed_one || !sign)) {
+2 -2
View File
@@ -153,7 +153,7 @@ public:
/** Sign a message with the given script */ /** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** Adds script and derivation path information to a PSBT, and optionally signs it. */ /** Adds script and derivation path information to a PSBT, and optionally signs it. */
virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; } virtual std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return common::PSBTError::UNSUPPORTED; }
virtual uint256 GetID() const { return uint256(); } virtual uint256 GetID() const { return uint256(); }
@@ -382,7 +382,7 @@ public:
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override; bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = SIGHASH_DEFAULT, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type = std::nullopt, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
uint256 GetID() const override; uint256 GetID() const override;
+2 -1
View File
@@ -192,7 +192,8 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
} }
auto psbt{*opt_psbt}; auto psbt{*opt_psbt};
const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)}; const PrecomputedTransactionData txdata{PrecomputePSBTData(psbt)};
const int sighash_type{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 150)}; std::optional<int> sighash_type{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 151)};
if (sighash_type == 151) sighash_type = std::nullopt;
auto sign = fuzzed_data_provider.ConsumeBool(); auto sign = fuzzed_data_provider.ConsumeBool();
auto bip32derivs = fuzzed_data_provider.ConsumeBool(); auto bip32derivs = fuzzed_data_provider.ConsumeBool();
auto finalize = fuzzed_data_provider.ConsumeBool(); auto finalize = fuzzed_data_provider.ConsumeBool();
+2 -2
View File
@@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data // Fill transaction with our data
bool complete = true; bool complete = true;
BOOST_REQUIRE(!m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true)); BOOST_REQUIRE(!m_wallet.FillPSBT(psbtx, complete, std::nullopt, false, true));
// Get the final tx // Get the final tx
DataStream ssTx{}; DataStream ssTx{};
@@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input // Try to sign the mutated input
SignatureData sigdata; SignatureData sigdata;
BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true, true)); BOOST_CHECK(m_wallet.FillPSBT(psbtx, complete, std::nullopt, true, true));
} }
BOOST_AUTO_TEST_CASE(parse_hd_keypath) BOOST_AUTO_TEST_CASE(parse_hd_keypath)
+2 -2
View File
@@ -2114,7 +2114,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
return false; return false;
} }
std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, std::optional<int> sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const
{ {
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
@@ -2157,7 +2157,7 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
} }
} }
RemoveUnnecessaryTransactions(psbtx, sighash_type); RemoveUnnecessaryTransactions(psbtx);
// Complete if every input is now signed // Complete if every input is now signed
complete = true; complete = true;
+1 -1
View File
@@ -662,7 +662,7 @@ public:
*/ */
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx, std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx,
bool& complete, bool& complete,
int sighash_type = SIGHASH_DEFAULT, std::optional<int> sighash_type = std::nullopt,
bool sign = true, bool sign = true,
bool bip32derivs = true, bool bip32derivs = true,
size_t* n_signed = nullptr, size_t* n_signed = nullptr,
+91 -1
View File
@@ -27,6 +27,7 @@ from test_framework.psbt import (
PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_UNSIGNED_TX,
PSBT_IN_RIPEMD160, PSBT_IN_RIPEMD160,
PSBT_IN_SHA256, PSBT_IN_SHA256,
PSBT_IN_SIGHASH_TYPE,
PSBT_IN_HASH160, PSBT_IN_HASH160,
PSBT_IN_HASH256, PSBT_IN_HASH256,
PSBT_IN_MUSIG2_PARTIAL_SIG, PSBT_IN_MUSIG2_PARTIAL_SIG,
@@ -37,7 +38,7 @@ from test_framework.psbt import (
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS, PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS,
PSBT_OUT_TAP_TREE, PSBT_OUT_TAP_TREE,
) )
from test_framework.script import CScript, OP_TRUE from test_framework.script import CScript, OP_TRUE, SIGHASH_ALL, SIGHASH_ANYONECANPAY
from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
@@ -274,6 +275,93 @@ class PSBTTest(BitcoinTestFramework):
assert "participant_pubkeys" in out_participant_pks assert "participant_pubkeys" in out_participant_pks
assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()]) assert_equal(out_participant_pks["participant_pubkeys"], [out_pubkey1.hex(), out_pubkey2.hex()])
def test_sighash_mismatch(self):
self.log.info("Test sighash type mismatches")
self.nodes[0].createwallet("sighash_mismatch")
wallet = self.nodes[0].get_wallet_rpc("sighash_mismatch")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
addr = wallet.getnewaddress(address_type="bech32")
def_wallet.sendtoaddress(addr, 5)
self.generate(self.nodes[0], 6)
# Retrieve the descriptors so we can do all of the tests with descriptorprocesspsbt as well
descs = wallet.listdescriptors(True)["descriptors"]
# Make a PSBT
psbt = wallet.walletcreatefundedpsbt([], [{def_wallet.getnewaddress(): 1}])["psbt"]
# Modify the PSBT and insert a sighash field for ALL|ANYONECANPAY on input 0
mod_psbt = PSBT.from_base64(psbt)
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL | SIGHASH_ANYONECANPAY).to_bytes(4, byteorder="little")
psbt = mod_psbt.to_base64()
# Mismatching sighash type fails, including when no type is specified
for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", wallet.walletprocesspsbt, psbt, True, sighash)
# Matching sighash type succeeds
proc = wallet.walletprocesspsbt(psbt, True, "ALL|ANYONECANPAY")
assert_equal(proc["complete"], True)
# Repeat with descriptorprocesspsbt
# Mismatching sighash type fails, including when no type is specified
for sighash in ["DEFAULT", "ALL", "NONE", "SINGLE", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", None]:
assert_raises_rpc_error(-22, "Specified sighash value does not match value stored in PSBT", self.nodes[0].descriptorprocesspsbt, psbt, descs, sighash)
# Matching sighash type succeeds
proc = self.nodes[0].descriptorprocesspsbt(psbt, descs, "ALL|ANYONECANPAY")
assert_equal(proc["complete"], True)
wallet.unloadwallet()
def test_sighash_adding(self):
self.log.info("Test adding of sighash type field")
self.nodes[0].createwallet("sighash_adding")
wallet = self.nodes[0].get_wallet_rpc("sighash_adding")
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
outputs = [{wallet.getnewaddress(address_type="bech32"): 1}]
outputs.append({wallet.getnewaddress(address_type="bech32m"): 1})
descs = wallet.listdescriptors(True)["descriptors"]
def_wallet.send(outputs)
self.generate(self.nodes[0], 6)
utxos = wallet.listunspent()
# Make a PSBT
psbt = wallet.walletcreatefundedpsbt(utxos, [{def_wallet.getnewaddress(): 0.5}])["psbt"]
# Process the PSBT with the wallet
wallet_psbt = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]
# Separately process the PSBT with descriptors
desc_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=descs, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]
for psbt in [wallet_psbt, desc_psbt]:
# Check that the PSBT has a sighash field on all inputs
dec_psbt = self.nodes[0].decodepsbt(psbt)
for input in dec_psbt["inputs"]:
assert_equal(input["sighash"], "ALL|ANYONECANPAY")
# Make sure we can still finalize the transaction
fin_res = self.nodes[0].finalizepsbt(psbt)
assert_equal(fin_res["complete"], True)
fin_hex = fin_res["hex"]
assert_equal(self.nodes[0].testmempoolaccept([fin_hex])[0]["allowed"], True)
# Change the sighash field to a different value and make sure we can no longer finalize
mod_psbt = PSBT.from_base64(psbt)
mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
mod_psbt.i[1].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
psbt = mod_psbt.to_base64()
fin_res = self.nodes[0].finalizepsbt(psbt)
assert_equal(fin_res["complete"], False)
self.nodes[0].sendrawtransaction(fin_hex)
self.generate(self.nodes[0], 1)
wallet.unloadwallet()
def assert_change_type(self, psbtx, expected_type): def assert_change_type(self, psbtx, expected_type):
"""Assert that the given PSBT has a change output with the given type.""" """Assert that the given PSBT has a change output with the given type."""
@@ -1121,6 +1209,8 @@ class PSBTTest(BitcoinTestFramework):
self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed") self.log.info("Test descriptorprocesspsbt raises if an invalid sighashtype is passed")
assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all") assert_raises_rpc_error(-8, "'all' is not a valid sighash parameter.", self.nodes[2].descriptorprocesspsbt, psbt, [descriptor], sighashtype="all")
self.test_sighash_mismatch()
self.test_sighash_adding()
if __name__ == '__main__': if __name__ == '__main__':
PSBTTest(__file__).main() PSBTTest(__file__).main()