mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-17 21:32:00 +01:00
Merge a7f76b28a1293929d05f15e0a9276d4ec3687c92 into 5f4422d68dc3530c353af1f87499de1c864b60ad
This commit is contained in:
commit
7a1a7facee
@ -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);
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -207,7 +207,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,
|
||||||
|
@ -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 {
|
||||||
|
116
src/psbt.cpp
116
src/psbt.cpp
@ -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;
|
||||||
|
// 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;
|
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;
|
||||||
|
12
src/psbt.h
12
src/psbt.h
@ -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};
|
||||||
|
|
||||||
@ -429,6 +432,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;
|
||||||
@ -1236,10 +1244,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);
|
||||||
|
@ -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;
|
||||||
|
@ -446,7 +446,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;
|
||||||
@ -503,7 +503,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);
|
||||||
|
|
||||||
@ -519,7 +519,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);
|
||||||
|
@ -535,7 +535,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& 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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
@ -1689,7 +1692,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{};
|
||||||
@ -1956,7 +1959,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();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,10 +378,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) {
|
||||||
|
@ -138,7 +138,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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -343,8 +343,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, false /* sign */, true /* bip32derivs */);
|
||||||
auto err{wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, true /* sign */, false /* bip32derivs */)};
|
auto err{wallet.FillPSBT(psbtx, complete, std::nullopt, true /* sign */, false /* bip32derivs */)};
|
||||||
if (err) return false;
|
if (err) return false;
|
||||||
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
||||||
return complete;
|
return complete;
|
||||||
|
@ -391,7 +391,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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
@ -951,12 +951,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;
|
||||||
@ -1169,7 +1172,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{};
|
||||||
@ -1623,7 +1626,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();
|
||||||
@ -1769,7 +1772,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);
|
||||||
}
|
}
|
||||||
|
@ -638,7 +638,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
|
|||||||
return SigningResult::SIGNING_FAILED;
|
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) {
|
if (n_signed) {
|
||||||
*n_signed = 0;
|
*n_signed = 0;
|
||||||
@ -651,11 +651,6 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
|
|||||||
continue;
|
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
|
// Check non_witness_utxo has specified prevout
|
||||||
if (input.non_witness_utxo) {
|
if (input.non_witness_utxo) {
|
||||||
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
|
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
|
// There's no UTXO so we can just skip this now
|
||||||
continue;
|
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);
|
bool signed_one = PSBTInputSigned(input);
|
||||||
if (n_signed && (signed_one || !sign)) {
|
if (n_signed && (signed_one || !sign)) {
|
||||||
@ -2565,7 +2563,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;
|
||||||
@ -2578,11 +2576,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()) {
|
||||||
@ -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);
|
bool signed_one = PSBTInputSigned(input);
|
||||||
if (n_signed && (signed_one || !sign)) {
|
if (n_signed && (signed_one || !sign)) {
|
||||||
|
@ -246,7 +246,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(); }
|
||||||
|
|
||||||
@ -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;
|
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;
|
||||||
|
|
||||||
@ -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;
|
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;
|
||||||
|
|
||||||
|
@ -188,7 +188,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();
|
||||||
|
@ -64,7 +64,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{};
|
||||||
@ -77,7 +77,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)
|
||||||
|
@ -2211,7 +2211,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;
|
||||||
@ -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 if every input is now signed
|
||||||
complete = true;
|
complete = true;
|
||||||
|
@ -663,7 +663,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,
|
||||||
|
@ -27,13 +27,14 @@ 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_NON_WITNESS_UTXO,
|
PSBT_IN_NON_WITNESS_UTXO,
|
||||||
PSBT_IN_WITNESS_UTXO,
|
PSBT_IN_WITNESS_UTXO,
|
||||||
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 (
|
||||||
@ -201,6 +202,100 @@ class PSBTTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
wallet.unloadwallet()
|
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):
|
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."""
|
||||||
|
|
||||||
@ -1051,6 +1146,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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user