Merge a7f76b28a1293929d05f15e0a9276d4ec3687c92 into 5f4422d68dc3530c353af1f87499de1c864b60ad

This commit is contained in:
Ava Chow 2025-03-17 03:55:30 +01:00 committed by GitHub
commit 7a1a7facee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 266 additions and 92 deletions

View File

@ -116,6 +116,10 @@ bilingual_str PSBTErrorString(PSBTError err)
return Untranslated("External signer failed to sign");
case PSBTError::UNSUPPORTED:
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
}
assert(false);

View File

@ -20,6 +20,8 @@ enum class PSBTError {
EXTERNAL_SIGNER_NOT_FOUND,
EXTERNAL_SIGNER_FAILED,
UNSUPPORTED,
INCOMPLETE,
OK,
};
} // namespace common

View File

@ -207,7 +207,7 @@ public:
int& num_blocks) = 0;
//! 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 bip32derivs,
size_t* n_signed,

View File

@ -64,7 +64,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Figure out what is missing
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
if (!complete) {
@ -124,7 +124,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i];
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;
break;
} else {

View File

@ -4,12 +4,15 @@
#include <psbt.h>
#include <common/types.h>
#include <node/types.h>
#include <policy/policy.h>
#include <script/signingprovider.h>
#include <util/check.h>
#include <util/strencodings.h>
using common::PSBTError;
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
{
inputs.resize(tx.vin.size());
@ -372,13 +375,13 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
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);
const CMutableTransaction& tx = *psbt.tx;
if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
return true;
return PSBTError::OK;
}
// 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.
COutPoint prevout = tx.vin[index].prevout;
if (prevout.n >= input.non_witness_utxo->vout.size()) {
return false;
return PSBTError::MISSING_INPUTS;
}
if (input.non_witness_utxo->GetHash() != prevout.hash) {
return false;
return PSBTError::MISSING_INPUTS;
}
utxo = input.non_witness_utxo->vout[prevout.n];
} else if (!input.witness_utxo.IsNull()) {
@ -407,7 +410,46 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
// a witness signature in this situation.
require_witness_sig = true;
} 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;
// 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;
}
Assert(sighash.has_value());
// 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.back() != *sighash) {
return PSBTError::SIGHASH_MISMATCH;
}
for (const auto& [_, sig] : input.m_tap_script_sigs) {
if (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;
@ -415,11 +457,11 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
if (txdata == nullptr) {
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
} 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);
}
// 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 (!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;
}
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
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
// Figure out if any non_witness_utxos should be dropped
std::vector<unsigned int> to_drop;
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
const auto& input = psbtx.inputs.at(i);
int wit_ver;
std::vector<unsigned char> wit_prog;
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
to_drop.clear();
break;
}
if (wit_ver == 0) {
// Segwit v0, so we cannot drop any non_witness_utxos
to_drop.clear();
break;
}
if (input.non_witness_utxo) {
to_drop.push_back(i);
}
// Figure out if any non_witness_utxos should be dropped
std::vector<unsigned int> to_drop;
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
const auto& input = psbtx.inputs.at(i);
int wit_ver;
std::vector<unsigned char> wit_prog;
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
// There's a non-segwit input, so we cannot drop any non_witness_utxos
to_drop.clear();
break;
}
if (wit_ver == 0) {
// Segwit v0, so we cannot drop any non_witness_utxos
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
// need to look at that field. If it is not present, then we can assume SIGHASH_DEFAULT or SIGHASH_ALL.
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
for (unsigned int i : to_drop) {
psbtx.inputs.at(i).non_witness_utxo = nullptr;
if (input.non_witness_utxo) {
to_drop.push_back(i);
}
}
// 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)
@ -486,7 +533,8 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
bool complete = true;
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
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;

View File

@ -5,6 +5,7 @@
#ifndef BITCOIN_PSBT_H
#define BITCOIN_PSBT_H
#include <common/types.h>
#include <node/transaction.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
@ -21,6 +22,8 @@ namespace node {
enum class TransactionError;
} // namespace node
using common::PSBTError;
// Magic bytes
static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
@ -429,6 +432,11 @@ struct PSBTInput
std::vector<unsigned char> 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
partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig)));
break;
@ -1236,10 +1244,10 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
* 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.
**/
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. */
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type);
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx);
/** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);

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.
if (m_wallet_model) {
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) {
showStatus(tr("Failed to load transaction: %1")
.arg(QString::fromStdString(PSBTErrorString(*err).translated)),
@ -83,7 +83,7 @@ void PSBTOperationsDialog::signTransaction()
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) {
showStatus(tr("Failed to sign transaction: %1")
@ -251,7 +251,7 @@ size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &p
size_t n_signed;
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) {
return 0;

View File

@ -446,7 +446,7 @@ void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
std::optional<PSBTError> err;
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) {
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
return false;
@ -503,7 +503,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
// 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(!err);
@ -519,7 +519,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool complete = false;
// Always fill without signing first. This prevents an external signer
// 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(!err);
send_failure = !signWithExternalSigner(psbtx, mtx, complete);

View File

@ -535,7 +535,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
// "Create Unsigned" clicked
PartiallySignedTransaction psbtx(mtx);
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) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
return false;

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.
// 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
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.
// We only actually care about those if our signing provider doesn't hide private
// 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.
@ -243,7 +246,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
UpdatePSBTOutput(provider, psbtx, i);
}
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
RemoveUnnecessaryTransactions(psbtx);
return psbtx;
}
@ -1689,7 +1692,7 @@ static RPCHelpMan utxoupdatepsbt()
request.params[0].get_str(),
request.context,
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
/*sighash_type=*/SIGHASH_ALL,
/*sighash_type=*/std::nullopt,
/*finalize=*/false);
DataStream ssTx{};
@ -1956,7 +1959,7 @@ RPCHelpMan descriptorprocesspsbt()
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 finalize = request.params[4].isNull() ? true : request.params[4].get_bool();

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)
{
int nHashType = ParseSighashString(hashType);
std::optional<int> nHashType = ParseSighashString(hashType);
if (!nHashType) {
nHashType = SIGHASH_DEFAULT;
}
// Script verification 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);
}

View File

@ -378,10 +378,10 @@ UniValue DescribeAddress(const CTxDestination& dest)
*
* @pre The sighash argument should be string or null.
*/
int ParseSighashString(const UniValue& sighash)
std::optional<int> ParseSighashString(const UniValue& sighash)
{
if (sighash.isNull()) {
return SIGHASH_DEFAULT;
return std::nullopt;
}
const auto result{SighashFromStr(sighash.get_str())};
if (!result) {

View File

@ -138,7 +138,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
UniValue DescribeAddress(const CTxDestination& dest);
/** 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.
unsigned int ParseConfirmTarget(const UniValue& value, unsigned int max_target);

View File

@ -238,6 +238,13 @@ bool CScript::IsPayToWitnessScriptHash() const
(*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
// followed by a data push between 2 and 40 bytes.
bool CScript::IsWitnessProgram(int& version, std::vector<unsigned char>& program) const

View File

@ -556,6 +556,8 @@ public:
bool IsPayToWitnessScriptHash() 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). */
bool IsPushOnly(const_iterator pc) const;
bool IsPushOnly() const;

View File

@ -79,7 +79,7 @@ util::Result<void> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestin
}
// 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) {
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize);

View File

@ -35,7 +35,7 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
*/
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
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H

View File

@ -343,8 +343,8 @@ bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) {
// First fill transaction with our data without signing,
// so external signers are not asked to sign more than once.
bool complete;
wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
auto err{wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */)};
wallet.FillPSBT(psbtx, complete, std::nullopt, false /* sign */, true /* bip32derivs */);
auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, true /* sign */, false /* bip32derivs */)};
if (err) return false;
complete = FinalizeAndExtractPSBT(psbtx, mtx);
return complete;

View File

@ -391,7 +391,7 @@ public:
}
return {};
}
std::optional<PSBTError> fillPSBT(int sighash_type,
std::optional<PSBTError> fillPSBT(std::optional<int> sighash_type,
bool sign,
bool bip32derivs,
size_t* n_signed,

View File

@ -100,8 +100,8 @@ static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const
// First fill transaction with our data without signing,
// so external signers are not asked to sign more than once.
bool complete;
pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true);
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/true, /*bip32derivs=*/false)};
pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/false, /*bip32derivs=*/true);
const auto err{pwallet->FillPSBT(psbtx, complete, std::nullopt, /*sign=*/true, /*bip32derivs=*/false)};
if (err) {
throw JSONRPCPSBTError(*err);
}
@ -951,12 +951,15 @@ RPCHelpMan signrawtransactionwithwallet()
// Parse the prevtxs array
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
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);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
return result;
@ -1169,7 +1172,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
} else {
PartiallySignedTransaction psbtx(mtx);
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(!complete);
DataStream ssTx{};
@ -1623,7 +1626,7 @@ RPCHelpMan walletprocesspsbt()
}
// 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
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
@ -1769,7 +1772,7 @@ RPCHelpMan walletcreatefundedpsbt()
// Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
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) {
throw JSONRPCPSBTError(*err);
}

View File

@ -638,7 +638,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
return SigningResult::SIGNING_FAILED;
}
std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, std::optional<int> sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
{
if (n_signed) {
*n_signed = 0;
@ -651,11 +651,6 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
continue;
}
// Get the Sighash type
if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) {
return PSBTError::SIGHASH_MISMATCH;
}
// Check non_witness_utxo has specified prevout
if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
@ -665,7 +660,10 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
// There's no UTXO so we can just skip this now
continue;
}
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
PSBTError res = SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
if (res != PSBTError::OK && res != PSBTError::INCOMPLETE) {
return res;
}
bool signed_one = PSBTInputSigned(input);
if (n_signed && (signed_one || !sign)) {
@ -2565,7 +2563,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
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) {
*n_signed = 0;
@ -2578,11 +2576,6 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
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
CScript script;
if (!input.witness_utxo.IsNull()) {
@ -2640,7 +2633,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);
if (n_signed && (signed_one || !sign)) {

View File

@ -246,7 +246,7 @@ public:
/** 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; };
/** 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(); }
@ -495,7 +495,7 @@ public:
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;
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;
@ -684,7 +684,7 @@ public:
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;
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;

View File

@ -188,7 +188,8 @@ FUZZ_TARGET(scriptpubkeyman, .init = initialize_spkm)
}
auto psbt{*opt_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 bip32derivs = fuzzed_data_provider.ConsumeBool();
auto finalize = fuzzed_data_provider.ConsumeBool();

View File

@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data
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
DataStream ssTx{};
@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input
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)

View File

@ -2211,7 +2211,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
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) {
*n_signed = 0;
@ -2254,7 +2254,7 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
}
}
RemoveUnnecessaryTransactions(psbtx, sighash_type);
RemoveUnnecessaryTransactions(psbtx);
// Complete if every input is now signed
complete = true;

View File

@ -663,7 +663,7 @@ public:
*/
std::optional<common::PSBTError> FillPSBT(PartiallySignedTransaction& psbtx,
bool& complete,
int sighash_type = SIGHASH_DEFAULT,
std::optional<int> sighash_type = std::nullopt,
bool sign = true,
bool bip32derivs = true,
size_t* n_signed = nullptr,

View File

@ -27,13 +27,14 @@ from test_framework.psbt import (
PSBT_GLOBAL_UNSIGNED_TX,
PSBT_IN_RIPEMD160,
PSBT_IN_SHA256,
PSBT_IN_SIGHASH_TYPE,
PSBT_IN_HASH160,
PSBT_IN_HASH256,
PSBT_IN_NON_WITNESS_UTXO,
PSBT_IN_WITNESS_UTXO,
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.test_framework import BitcoinTestFramework
from test_framework.util import (
@ -201,6 +202,100 @@ class PSBTTest(BitcoinTestFramework):
wallet.unloadwallet()
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
if self.options.descriptors:
descs = wallet.listdescriptors(True)["descriptors"]
else:
descs = [descsum_create(f"wpkh({wallet.dumpprivkey(addr)})")]
# 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)
addr = wallet.getnewaddress(address_type="bech32")
outputs = [{addr: 1}]
if self.options.descriptors:
outputs.append({wallet.getnewaddress(address_type="bech32m"): 1})
descs = wallet.listdescriptors(True)["descriptors"]
else:
descs = [descsum_create(f"wpkh({wallet.dumpprivkey(addr)})")]
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"]
# 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")
if self.options.descriptors:
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):
"""Assert that the given PSBT has a change output with the given type."""
@ -1051,6 +1146,8 @@ class PSBTTest(BitcoinTestFramework):
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")
self.test_sighash_mismatch()
self.test_sighash_adding()
if __name__ == '__main__':
PSBTTest(__file__).main()