Merge bitcoin/bitcoin#31622: psbt: add non-default sighash types to PSBTs and unify sighash type match checking
ee045b61efrpc, psbt: Require sighashes match for descriptorprocesspsbt (Ava Chow)2b7682c372psbt: use sighash type field to determine whether to remove non-witness utxos (Ava Chow)28781b5f06psbt: Add sighash types to PSBT when not DEFAULT or ALL (Ava Chow)15ce1bd73fpsbt: Enforce sighash type of signatures matches psbt (Ava Chow)1f71cd337awallet: Remove sighash type enforcement from FillPSBT (Ava Chow)4c7d767e49psbt: Check sighash types in SignPSBTInput and take sighash as optional (Ava Chow)a118256948script: Add IsPayToTaproot() (Ava Chow)d6001dcd4awallet: change FillPSBT to take sighash as optional (Ava Chow)e58b680923psbt: Return PSBTError from SignPSBTInput (Ava Chow)2adfd81532tests: Test PSBT sighash type mismatch (Ava Chow)5a5d26d612psbt: 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-ACKee045b61efrkrux: re-ACKee045b61effjahr: Code review ACKee045b61efTree-SHA512: 4ead5be1ef6756251b827f594beba868a145d75bf7f4ef6f15ad21f0ae4b8d71b38c83494e5a6b75f37fadd097178cddd93d614b962a2c72fc134f00ba2f74ae
This commit is contained in:
27 files changed
+253
-84
No files matched your search
@@ -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);
|
||||
|
||||
@@ -20,6 +20,8 @@ enum class PSBTError {
|
||||
EXTERNAL_SIGNER_NOT_FOUND,
|
||||
EXTERNAL_SIGNER_FAILED,
|
||||
UNSUPPORTED,
|
||||
INCOMPLETE,
|
||||
OK,
|
||||
};
|
||||
} // namespace common
|
||||
|
||||
|
||||
@@ -205,7 +205,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,
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+82
-34
@@ -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;
|
||||
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;
|
||||
@@ -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;
|
||||
|
||||
+10
-2
@@ -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};
|
||||
|
||||
@@ -520,6 +523,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;
|
||||
@@ -1396,10 +1404,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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -450,7 +450,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;
|
||||
@@ -507,7 +507,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);
|
||||
|
||||
@@ -523,7 +523,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);
|
||||
|
||||
@@ -521,7 +521,7 @@ bool WalletModel::bumpFee(Txid hash, Txid& 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1802,7 +1805,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{};
|
||||
@@ -2072,7 +2075,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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -357,10 +357,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) {
|
||||
|
||||
+1
-1
@@ -137,7 +137,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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -338,8 +338,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, /*sign=*/false, /*bip32derivs=*/true);
|
||||
auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, /*sign=*/true, /*bip32derivs=*/false)};
|
||||
if (err) return false;
|
||||
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
||||
return complete;
|
||||
|
||||
@@ -383,7 +383,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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -960,12 +960,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;
|
||||
@@ -1179,7 +1182,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{};
|
||||
@@ -1635,7 +1638,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();
|
||||
@@ -1784,7 +1787,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);
|
||||
}
|
||||
|
||||
@@ -1311,7 +1311,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;
|
||||
@@ -1324,11 +1324,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()) {
|
||||
@@ -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);
|
||||
if (n_signed && (signed_one || !sign)) {
|
||||
|
||||
@@ -153,7 +153,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(); }
|
||||
|
||||
@@ -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;
|
||||
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;
|
||||
|
||||
|
||||
@@ -192,7 +192,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();
|
||||
|
||||
@@ -65,7 +65,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{};
|
||||
@@ -78,7 +78,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)
|
||||
|
||||
@@ -2114,7 +2114,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;
|
||||
@@ -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 = true;
|
||||
|
||||
+1
-1
@@ -662,7 +662,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,
|
||||
|
||||
@@ -27,6 +27,7 @@ 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_MUSIG2_PARTIAL_SIG,
|
||||
@@ -37,7 +38,7 @@ from test_framework.psbt import (
|
||||
PSBT_OUT_MUSIG2_PARTICIPANT_PUBKEYS,
|
||||
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 (
|
||||
@@ -274,6 +275,93 @@ class PSBTTest(BitcoinTestFramework):
|
||||
assert "participant_pubkeys" in out_participant_pks
|
||||
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):
|
||||
"""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")
|
||||
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()
|
||||
Reference in new issue
Block a user