This commit is contained in:
Ava Chow 2025-04-29 12:09:50 +02:00 committed by GitHub
commit 58c390808a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1049 additions and 249 deletions

View file

@ -69,8 +69,8 @@ def signet_txs(block, challenge):
def decode_psbt(b64psbt): def decode_psbt(b64psbt):
psbt = PSBT.from_base64(b64psbt) psbt = PSBT.from_base64(b64psbt)
assert len(psbt.tx.vin) == 1 assert len(psbt.i) == 1
assert len(psbt.tx.vout) == 1 assert len(psbt.i) == 1
assert PSBT_SIGNET_BLOCK in psbt.g.map assert PSBT_SIGNET_BLOCK in psbt.g.map
scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"") scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"")

View file

@ -0,0 +1,6 @@
Updated RPCs
------------
- `createpsbt` and `walletcreatepsbt` will now default to creating
version 2 PSBTs. An optional `psbt_version` argument is added to
both which allows specifying the version of PSBT to create.

View file

@ -22,11 +22,11 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
CAmount in_amt = 0; CAmount in_amt = 0;
result.inputs.resize(psbtx.tx->vin.size()); result.inputs.resize(psbtx.inputs.size());
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.inputs.size(); ++i) {
PSBTInput& input = psbtx.inputs[i]; PSBTInput& input = psbtx.inputs[i];
PSBTInputAnalysis& input_analysis = result.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i];
@ -35,7 +35,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Check for a UTXO // Check for a UTXO
CTxOut utxo; CTxOut utxo;
if (psbtx.GetInputUTXO(utxo, i)) { if (input.GetUTXO(utxo)) {
if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) { if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) {
result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i)); result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i));
return result; return result;
@ -43,7 +43,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
in_amt += utxo.nValue; in_amt += utxo.nValue;
input_analysis.has_utxo = true; input_analysis.has_utxo = true;
} else { } else {
if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { if (input.non_witness_utxo && input.prev_out >= input.non_witness_utxo->vout.size()) {
result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i)); result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i));
return result; return result;
} }
@ -89,7 +89,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
// Calculate next role for PSBT by grabbing "minimum" PSBTInput next role // Calculate next role for PSBT by grabbing "minimum" PSBTInput next role
result.next = PSBTRole::EXTRACTOR; result.next = PSBTRole::EXTRACTOR;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
PSBTInputAnalysis& input_analysis = result.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i];
result.next = std::min(result.next, input_analysis.next); result.next = std::min(result.next, input_analysis.next);
} }
@ -97,12 +97,12 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
if (calc_fee) { if (calc_fee) {
// Get the output amount // Get the output amount
CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), CAmount out_amt = std::accumulate(psbtx.outputs.begin(), psbtx.outputs.end(), CAmount(0),
[](CAmount a, const CTxOut& b) { [](CAmount a, const PSBTOutput& b) {
if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) { if (!MoneyRange(a) || !MoneyRange(*b.amount) || !MoneyRange(a + *b.amount)) {
return CAmount(-1); return CAmount(-1);
} }
return a += b.nValue; return a += *b.amount;
} }
); );
if (!MoneyRange(out_amt)) { if (!MoneyRange(out_amt)) {
@ -115,23 +115,23 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
result.fee = fee; result.fee = fee;
// Estimate the size // Estimate the size
CMutableTransaction mtx(*psbtx.tx); CMutableTransaction mtx(psbtx.GetUnsignedTx());
CCoinsView view_dummy; CCoinsView view_dummy;
CCoinsViewCache view(&view_dummy); CCoinsViewCache view(&view_dummy);
bool success = true; bool success = true;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
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, 1) || !input.GetUTXO(newcoin.out)) {
success = false; success = false;
break; break;
} else { } else {
mtx.vin[i].scriptSig = input.final_script_sig; mtx.vin[i].scriptSig = input.final_script_sig;
mtx.vin[i].scriptWitness = input.final_script_witness; mtx.vin[i].scriptWitness = input.final_script_witness;
newcoin.nHeight = 1; newcoin.nHeight = 1;
view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true); view.AddCoin(input.GetOutPoint(), std::move(newcoin), true);
} }
} }

View file

@ -6,14 +6,19 @@
#include <node/types.h> #include <node/types.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <primitives/transaction.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>
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx) PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx, uint32_t version) : m_version(version)
{ {
inputs.resize(tx.vin.size()); if (version == 0) {
outputs.resize(tx.vout.size()); this->tx = tx;
}
inputs.resize(tx.vin.size(), PSBTInput(GetVersion()));
outputs.resize(tx.vout.size(), PSBTOutput(GetVersion()));
SetupFromTx(tx);
} }
bool PartiallySignedTransaction::IsNull() const bool PartiallySignedTransaction::IsNull() const
@ -24,10 +29,13 @@ bool PartiallySignedTransaction::IsNull() const
bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
{ {
// Prohibited to merge two PSBTs over different transactions // Prohibited to merge two PSBTs over different transactions
if (tx->GetHash() != psbt.tx->GetHash()) { if (GetUniqueID() != psbt.GetUniqueID()) {
return false; return false;
} }
if (!Assume(*tx_version == psbt.tx_version)) {
return false;
}
for (unsigned int i = 0; i < inputs.size(); ++i) { for (unsigned int i = 0; i < inputs.size(); ++i) {
inputs[i].Merge(psbt.inputs[i]); inputs[i].Merge(psbt.inputs[i]);
} }
@ -41,51 +49,229 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end()); m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
} }
} }
if (fallback_locktime == std::nullopt && psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;
if (m_tx_modifiable != std::nullopt && psbt.m_tx_modifiable != std::nullopt) *m_tx_modifiable |= *psbt.m_tx_modifiable;
if (m_tx_modifiable == std::nullopt && psbt.m_tx_modifiable != std::nullopt) m_tx_modifiable = psbt.m_tx_modifiable;
unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
return true; return true;
} }
bool PartiallySignedTransaction::AddInput(const CTxIn& txin, PSBTInput& psbtin) bool PartiallySignedTransaction::ComputeTimeLock(uint32_t& locktime) const
{ {
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) { std::optional<uint32_t> time_lock{0};
std::optional<uint32_t> height_lock{0};
for (const PSBTInput& input : inputs) {
if (input.time_locktime != std::nullopt && input.height_locktime == std::nullopt) {
height_lock.reset(); // Transaction can no longer have a height locktime
if (time_lock == std::nullopt) {
return false;
}
} else if (input.time_locktime == std::nullopt && input.height_locktime != std::nullopt) {
time_lock.reset(); // Transaction can no longer have a time locktime
if (height_lock == std::nullopt) {
return false;
}
}
if (input.time_locktime && time_lock != std::nullopt) {
time_lock = std::max(time_lock, input.time_locktime);
}
if (input.height_locktime && height_lock != std::nullopt) {
height_lock = std::max(height_lock, input.height_locktime);
}
}
if (height_lock != std::nullopt && *height_lock > 0) {
locktime = *height_lock;
return true;
}
if (time_lock != std::nullopt && *time_lock > 0) {
locktime = *time_lock;
return true;
}
locktime = fallback_locktime.value_or(0);
return true;
}
CMutableTransaction PartiallySignedTransaction::GetUnsignedTx() const
{
if (tx != std::nullopt) {
return *tx;
}
CMutableTransaction mtx;
mtx.version = *tx_version;
bool locktime_success = ComputeTimeLock(mtx.nLockTime);
assert(locktime_success);
uint32_t max_sequence = CTxIn::SEQUENCE_FINAL;
for (const PSBTInput& input : inputs) {
CTxIn txin;
txin.prevout.hash = input.prev_txid;
txin.prevout.n = *input.prev_out;
txin.nSequence = input.sequence.value_or(max_sequence);
mtx.vin.push_back(txin);
}
for (const PSBTOutput& output : outputs) {
CTxOut txout;
txout.nValue = *output.amount;
txout.scriptPubKey = *output.script;
mtx.vout.push_back(txout);
}
return mtx;
}
uint256 PartiallySignedTransaction::GetUniqueID() const
{
if (m_version == 0) {
return tx->GetHash();
}
// Get the unsigned transaction
CMutableTransaction mtx = GetUnsignedTx();
// Set the sequence numbers to 0
for (CTxIn& txin : mtx.vin) {
txin.nSequence = 0;
}
return mtx.GetHash();
}
bool PartiallySignedTransaction::AddInput(PSBTInput& psbtin)
{
if (m_version == std::nullopt || m_version == 0) {
// This is a v0 psbt, so do the v0 AddInput
CTxIn txin(COutPoint(psbtin.prev_txid, *psbtin.prev_out));
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
// Prevent duplicate inputs
return false;
}
tx->vin.push_back(txin);
psbtin.partial_sigs.clear();
psbtin.final_script_sig.clear();
psbtin.final_script_witness.SetNull();
inputs.push_back(psbtin);
return true;
}
if (m_version >= 2) {
if (psbtin.prev_txid.IsNull() || psbtin.prev_out == std::nullopt) {
return false;
}
// Prevent duplicate inputs
if (std::find_if(inputs.begin(), inputs.end(),
[psbtin](const PSBTInput& psbt) {
return psbt.prev_txid == psbtin.prev_txid && psbt.prev_out == psbtin.prev_out;
}
) != inputs.end()) {
return false;
}
// Check inputs modifiable flag
if (m_tx_modifiable == std::nullopt || !m_tx_modifiable->test(0)) {
return false;
}
// Determine if we need to iterate the inputs.
// For now, we only do this if the new input has a required time lock.
// The BIP states that we should also do this if m_tx_modifiable's bit 2 is set
// (Has SIGHASH_SINGLE flag) but since we are only adding inputs at the end of the vector,
// we don't care about that.
bool iterate_inputs = psbtin.time_locktime != std::nullopt || psbtin.height_locktime != std::nullopt;
if (iterate_inputs) {
uint32_t old_timelock;
if (!ComputeTimeLock(old_timelock)) {
return false;
}
std::optional<uint32_t> time_lock = psbtin.time_locktime;
std::optional<uint32_t> height_lock = psbtin.height_locktime;
bool has_sigs = false;
for (const PSBTInput& input : inputs) {
if (input.time_locktime != std::nullopt && input.height_locktime == std::nullopt) {
height_lock.reset(); // Transaction can no longer have a height locktime
if (time_lock == std::nullopt) {
return false;
}
} else if (input.time_locktime == std::nullopt && input.height_locktime != std::nullopt) {
time_lock.reset(); // Transaction can no longer have a time locktime
if (height_lock == std::nullopt) {
return false;
}
}
if (input.time_locktime && time_lock != std::nullopt) {
time_lock = std::max(time_lock, input.time_locktime);
}
if (input.height_locktime && height_lock != std::nullopt) {
height_lock = std::max(height_lock, input.height_locktime);
}
if (!input.partial_sigs.empty()) {
has_sigs = true;
}
}
uint32_t new_timelock = fallback_locktime.value_or(0);
if (height_lock != std::nullopt && *height_lock > 0) {
new_timelock = *height_lock;
} else if (time_lock != std::nullopt && *time_lock > 0) {
new_timelock = *time_lock;
}
if (has_sigs && old_timelock != new_timelock) {
return false;
}
}
// Add the input to the end
inputs.push_back(psbtin);
return true;
}
return false;
}
bool PartiallySignedTransaction::AddOutput(const PSBTOutput& psbtout)
{
if (psbtout.amount == std::nullopt || !psbtout.script.has_value()) {
return false; return false;
} }
tx->vin.push_back(txin);
psbtin.partial_sigs.clear();
psbtin.final_script_sig.clear();
psbtin.final_script_witness.SetNull();
inputs.push_back(psbtin);
return true;
}
bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput& psbtout) if (tx != std::nullopt) {
{ // This is a v0 psbt, do the v0 AddOutput
tx->vout.push_back(txout); CTxOut txout(*psbtout.amount, *psbtout.script);
tx->vout.push_back(txout);
outputs.push_back(psbtout);
return true;
}
// No global tx, must be PSBTv2
// Check outputs are modifiable
if (m_tx_modifiable == std::nullopt || !m_tx_modifiable->test(1)) {
return false;
}
outputs.push_back(psbtout); outputs.push_back(psbtout);
return true; return true;
} }
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const bool PSBTInput::GetUTXO(CTxOut& utxo) const
{ {
const PSBTInput& input = inputs[input_index]; if (non_witness_utxo) {
uint32_t prevout_index = tx->vin[input_index].prevout.n; if (prev_out >= non_witness_utxo->vout.size()) {
if (input.non_witness_utxo) {
if (prevout_index >= input.non_witness_utxo->vout.size()) {
return false; return false;
} }
if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) { if (non_witness_utxo->GetHash() != prev_txid) {
return false; return false;
} }
utxo = input.non_witness_utxo->vout[prevout_index]; utxo = non_witness_utxo->vout[*prev_out];
} else if (!input.witness_utxo.IsNull()) { } else if (!witness_utxo.IsNull()) {
utxo = input.witness_utxo; utxo = witness_utxo;
} else { } else {
return false; return false;
} }
return true; return true;
} }
COutPoint PSBTInput::GetOutPoint() const
{
return COutPoint(prev_txid, *prev_out);
}
bool PSBTInput::IsNull() const bool PSBTInput::IsNull() const
{ {
return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty(); return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty();
@ -197,6 +383,9 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata)
void PSBTInput::Merge(const PSBTInput& input) void PSBTInput::Merge(const PSBTInput& input)
{ {
assert(prev_txid == input.prev_txid);
assert(*prev_out == *input.prev_out);
if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo;
if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) {
witness_utxo = input.witness_utxo; witness_utxo = input.witness_utxo;
@ -220,6 +409,9 @@ void PSBTInput::Merge(const PSBTInput& input)
if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig; if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig;
if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key; if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key;
if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root; if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root;
if (sequence == std::nullopt && input.sequence != std::nullopt) sequence = input.sequence;
if (time_locktime == std::nullopt && input.time_locktime != std::nullopt) time_locktime = input.time_locktime;
if (height_locktime == std::nullopt && input.height_locktime != std::nullopt) height_locktime = input.height_locktime;
} }
void PSBTOutput::FillSignatureData(SignatureData& sigdata) const void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
@ -280,6 +472,9 @@ bool PSBTOutput::IsNull() const
void PSBTOutput::Merge(const PSBTOutput& output) void PSBTOutput::Merge(const PSBTOutput& output)
{ {
assert(*amount == *output.amount);
assert(*script == *output.script);
hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end());
unknown.insert(output.unknown.begin(), output.unknown.end()); unknown.insert(output.unknown.begin(), output.unknown.end());
m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end()); m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end());
@ -303,7 +498,7 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
// 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 = psbt.tx->vin[input_index].prevout; COutPoint prevout = input.GetOutPoint();
if (prevout.n >= input.non_witness_utxo->vout.size()) { if (prevout.n >= input.non_witness_utxo->vout.size()) {
return false; return false;
} }
@ -317,10 +512,11 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
return false; return false;
} }
const CMutableTransaction tx = psbt.GetUnsignedTx();
if (txdata) { if (txdata) {
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL}); return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&tx, input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL});
} else { } else {
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, MissingDataBehavior::FAIL}); return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&tx, input_index, utxo.nValue, MissingDataBehavior::FAIL});
} }
} }
@ -337,7 +533,7 @@ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index) void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index)
{ {
CMutableTransaction& tx = *Assert(psbt.tx); CMutableTransaction tx = psbt.GetUnsignedTx();
const CTxOut& out = tx.vout.at(index); const CTxOut& out = tx.vout.at(index);
PSBTOutput& psbt_out = psbt.outputs.at(index); PSBTOutput& psbt_out = psbt.outputs.at(index);
@ -357,11 +553,11 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
{ {
const CMutableTransaction& tx = *psbt.tx; const CMutableTransaction& tx = psbt.GetUnsignedTx();
bool have_all_spent_outputs = true; bool have_all_spent_outputs = true;
std::vector<CTxOut> utxos(tx.vin.size()); std::vector<CTxOut> utxos;
for (size_t idx = 0; idx < tx.vin.size(); ++idx) { for (const PSBTInput& input : psbt.inputs) {
if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false; if (!input.GetUTXO(utxos.emplace_back())) have_all_spent_outputs = false;
} }
PrecomputedTransactionData txdata; PrecomputedTransactionData txdata;
if (have_all_spent_outputs) { if (have_all_spent_outputs) {
@ -375,7 +571,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize) bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, 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.GetUnsignedTx();
if (PSBTInputSignedAndVerified(psbt, index, txdata)) { if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
return true; return true;
@ -391,7 +587,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
// 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 = input.GetOutPoint();
if (prevout.n >= input.non_witness_utxo->vout.size()) { if (prevout.n >= input.non_witness_utxo->vout.size()) {
return false; return false;
} }
@ -485,7 +681,7 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
// script. // script.
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.inputs.size(); ++i) {
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true); complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
} }
@ -500,7 +696,7 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
return false; return false;
} }
result = *psbtx.tx; result = psbtx.GetUnsignedTx();
for (unsigned int i = 0; i < result.vin.size(); ++i) { for (unsigned int i = 0; i < result.vin.size(); ++i) {
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig; result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
@ -566,3 +762,36 @@ uint32_t PartiallySignedTransaction::GetVersion() const
} }
return 0; return 0;
} }
void PartiallySignedTransaction::SetupFromTx(const CMutableTransaction& tx)
{
tx_version = tx.version;
fallback_locktime = tx.nLockTime;
uint32_t i;
for (i = 0; i < tx.vin.size(); ++i) {
PSBTInput& input = inputs.at(i);
const CTxIn& txin = tx.vin.at(i);
input.prev_txid = txin.prevout.hash;
input.prev_out = txin.prevout.n;
input.sequence = txin.nSequence;
}
for (i = 0; i < tx.vout.size(); ++i) {
PSBTOutput& output = outputs.at(i);
const CTxOut& txout = tx.vout.at(i);
output.amount = txout.nValue;
output.script = txout.scriptPubKey;
}
}
void PartiallySignedTransaction::CacheUnsignedTxPieces()
{
// To make things easier, we split up the global unsigned transaction
// and use the PSBTv2 fields for PSBTv0.
if (tx != std::nullopt) {
SetupFromTx(*tx);
}
}

View file

@ -14,8 +14,10 @@
#include <script/signingprovider.h> #include <script/signingprovider.h>
#include <span.h> #include <span.h>
#include <streams.h> #include <streams.h>
#include <uint256.h>
#include <optional> #include <optional>
#include <bitset>
namespace node { namespace node {
enum class TransactionError; enum class TransactionError;
@ -27,6 +29,11 @@ static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
// Global types // Global types
static constexpr uint8_t PSBT_GLOBAL_UNSIGNED_TX = 0x00; static constexpr uint8_t PSBT_GLOBAL_UNSIGNED_TX = 0x00;
static constexpr uint8_t PSBT_GLOBAL_XPUB = 0x01; static constexpr uint8_t PSBT_GLOBAL_XPUB = 0x01;
static constexpr uint8_t PSBT_GLOBAL_TX_VERSION = 0x02;
static constexpr uint8_t PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03;
static constexpr uint8_t PSBT_GLOBAL_INPUT_COUNT = 0x04;
static constexpr uint8_t PSBT_GLOBAL_OUTPUT_COUNT = 0x05;
static constexpr uint8_t PSBT_GLOBAL_TX_MODIFIABLE = 0x06;
static constexpr uint8_t PSBT_GLOBAL_VERSION = 0xFB; static constexpr uint8_t PSBT_GLOBAL_VERSION = 0xFB;
static constexpr uint8_t PSBT_GLOBAL_PROPRIETARY = 0xFC; static constexpr uint8_t PSBT_GLOBAL_PROPRIETARY = 0xFC;
@ -44,6 +51,11 @@ static constexpr uint8_t PSBT_IN_RIPEMD160 = 0x0A;
static constexpr uint8_t PSBT_IN_SHA256 = 0x0B; static constexpr uint8_t PSBT_IN_SHA256 = 0x0B;
static constexpr uint8_t PSBT_IN_HASH160 = 0x0C; static constexpr uint8_t PSBT_IN_HASH160 = 0x0C;
static constexpr uint8_t PSBT_IN_HASH256 = 0x0D; static constexpr uint8_t PSBT_IN_HASH256 = 0x0D;
static constexpr uint8_t PSBT_IN_PREVIOUS_TXID = 0x0e;
static constexpr uint8_t PSBT_IN_OUTPUT_INDEX = 0x0f;
static constexpr uint8_t PSBT_IN_SEQUENCE = 0x10;
static constexpr uint8_t PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11;
static constexpr uint8_t PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12;
static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13; static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13;
static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14; static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14;
static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15; static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
@ -59,6 +71,8 @@ static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC;
static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00; static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00;
static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01; static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01;
static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
static constexpr uint8_t PSBT_OUT_AMOUNT = 0x03;
static constexpr uint8_t PSBT_OUT_SCRIPT = 0x04;
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05; static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06; static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07; static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
@ -74,7 +88,7 @@ static constexpr uint8_t PSBT_SEPARATOR = 0x00;
const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MB const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MB
// PSBT version number // PSBT version number
static constexpr uint32_t PSBT_HIGHEST_VERSION = 0; static constexpr uint32_t PSBT_HIGHEST_VERSION = 2;
/** A structure for PSBT proprietary types */ /** A structure for PSBT proprietary types */
struct PSBTProprietary struct PSBTProprietary
@ -256,6 +270,12 @@ struct PSBTInput
std::map<uint160, std::vector<unsigned char>> hash160_preimages; std::map<uint160, std::vector<unsigned char>> hash160_preimages;
std::map<uint256, std::vector<unsigned char>> hash256_preimages; std::map<uint256, std::vector<unsigned char>> hash256_preimages;
Txid prev_txid;
std::optional<uint32_t> prev_out;
std::optional<uint32_t> sequence;
std::optional<uint32_t> time_locktime;
std::optional<uint32_t> height_locktime;
// Taproot fields // Taproot fields
std::vector<unsigned char> m_tap_key_sig; std::vector<unsigned char> m_tap_key_sig;
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs; std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs;
@ -275,11 +295,21 @@ struct PSBTInput
std::set<PSBTProprietary> m_proprietary; std::set<PSBTProprietary> m_proprietary;
std::optional<int> sighash_type; std::optional<int> sighash_type;
uint32_t m_psbt_version;
bool IsNull() const; bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const; void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata); void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTInput& input); void Merge(const PSBTInput& input);
PSBTInput() = default; /**
* Retrieves the UTXO for this input
*
* @param[out] utxo The UTXO of this input
* @return Whether the UTXO could be retrieved
*/
bool GetUTXO(CTxOut& utxo) const;
COutPoint GetOutPoint() const;
PSBTInput(uint32_t psbt_version) : m_psbt_version(psbt_version) {}
template <typename Stream> template <typename Stream>
inline void Serialize(Stream& s) const { inline void Serialize(Stream& s) const {
@ -441,6 +471,31 @@ struct PSBTInput
SerializeToVector(s, final_script_witness.stack); SerializeToVector(s, final_script_witness.stack);
} }
// Write PSBTv2 fields
if (m_psbt_version >= 2) {
// Write prev txid, vout, sequence, and lock times
if (!prev_txid.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_PREVIOUS_TXID));
SerializeToVector(s, prev_txid);
}
if (prev_out != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_OUTPUT_INDEX));
SerializeToVector(s, *prev_out);
}
if (sequence != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_SEQUENCE));
SerializeToVector(s, *sequence);
}
if (time_locktime != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_REQUIRED_TIME_LOCKTIME));
SerializeToVector(s, *time_locktime);
}
if (height_locktime != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_REQUIRED_HEIGHT_LOCKTIME));
SerializeToVector(s, *height_locktime);
}
}
// Write proprietary things // Write proprietary things
for (const auto& entry : m_proprietary) { for (const auto& entry : m_proprietary) {
s << entry.key; s << entry.key;
@ -663,6 +718,80 @@ struct PSBTInput
hash256_preimages.emplace(hash, std::move(preimage)); hash256_preimages.emplace(hash, std::move(preimage));
break; break;
} }
case PSBT_IN_PREVIOUS_TXID:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, previous txid is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Previous txid key is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Previous txid is not allowed in PSBTv0");
}
UnserializeFromVector(s, prev_txid);
break;
}
case PSBT_IN_OUTPUT_INDEX:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, previous output's index is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Previous output's index is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Previous output's index is not allowed in PSBTv0");
}
uint32_t v;
UnserializeFromVector(s, v);
prev_out = v;
break;
}
case PSBT_IN_SEQUENCE:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, sequence is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Sequence key is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Sequence is not allowed in PSBTv0");
}
uint32_t v;
UnserializeFromVector(s, v);
sequence = v;
break;
}
case PSBT_IN_REQUIRED_TIME_LOCKTIME:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, required time based locktime is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Required time based locktime is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Required time based locktime is not allowed in PSBTv0");
}
uint32_t v;
UnserializeFromVector(s, v);
if (v < LOCKTIME_THRESHOLD) {
throw std::ios_base::failure("Required time based locktime is invalid (less than 500000000)");
}
time_locktime = v;
break;
}
case PSBT_IN_REQUIRED_HEIGHT_LOCKTIME:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, required height based locktime is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Required height based locktime is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Required height based locktime is not allowed in PSBTv0");
}
uint32_t v;
UnserializeFromVector(s, v);
if (v >= LOCKTIME_THRESHOLD) {
throw std::ios_base::failure("Required time based locktime is invalid (greater than or equal to 500000000)");
}
height_locktime = v;
break;
}
case PSBT_IN_TAP_KEY_SIG: case PSBT_IN_TAP_KEY_SIG:
{ {
if (!key_lookup.emplace(key).second) { if (!key_lookup.emplace(key).second) {
@ -840,6 +969,16 @@ struct PSBTInput
if (!found_sep) { if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of an input map"); throw std::ios_base::failure("Separator is missing at the end of an input map");
} }
// Make sure required PSBTv2 fields are present
if (m_psbt_version >= 2) {
if (prev_txid.IsNull()) {
throw std::ios_base::failure("Previous TXID is required in PSBTv2");
}
if (prev_out == std::nullopt) {
throw std::ios_base::failure("Previous output's index is required in PSBTv2");
}
}
} }
template <typename Stream> template <typename Stream>
@ -854,18 +993,25 @@ struct PSBTOutput
CScript redeem_script; CScript redeem_script;
CScript witness_script; CScript witness_script;
std::map<CPubKey, KeyOriginInfo> hd_keypaths; std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::optional<CAmount> amount;
std::optional<CScript> script;
XOnlyPubKey m_tap_internal_key; XOnlyPubKey m_tap_internal_key;
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree; std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths; std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants; std::map<CPubKey, std::vector<CPubKey>> m_musig2_participants;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::set<PSBTProprietary> m_proprietary; std::set<PSBTProprietary> m_proprietary;
uint32_t m_psbt_version;
bool IsNull() const; bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const; void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata); void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTOutput& output); void Merge(const PSBTOutput& output);
PSBTOutput() = default; PSBTOutput(uint32_t psbt_version) : m_psbt_version(psbt_version) {}
template <typename Stream> template <typename Stream>
inline void Serialize(Stream& s) const { inline void Serialize(Stream& s) const {
@ -884,6 +1030,18 @@ struct PSBTOutput
// Write any hd keypaths // Write any hd keypaths
SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_OUT_BIP32_DERIVATION)); SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_OUT_BIP32_DERIVATION));
if (m_psbt_version >= 2) {
// Write amount and spk
if (amount != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_AMOUNT));
SerializeToVector(s, *amount);
}
if (script.has_value()) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_SCRIPT));
s << *script;
}
}
// Write proprietary things // Write proprietary things
for (const auto& entry : m_proprietary) { for (const auto& entry : m_proprietary) {
s << entry.key; s << entry.key;
@ -991,6 +1149,34 @@ struct PSBTOutput
DeserializeHDKeypaths(s, key, hd_keypaths); DeserializeHDKeypaths(s, key, hd_keypaths);
break; break;
} }
case PSBT_OUT_AMOUNT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output amount is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output amount key is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Output amount is not allowed in PSBTv0");
}
CAmount v;
UnserializeFromVector(s, v);
amount = v;
break;
}
case PSBT_OUT_SCRIPT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output script is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output script key is more than one byte type");
} else if (m_psbt_version == 0) {
throw std::ios_base::failure("Output script is not allowed in PSBTv0");
}
CScript v;
s >> v;
script = v;
break;
}
case PSBT_OUT_TAP_INTERNAL_KEY: case PSBT_OUT_TAP_INTERNAL_KEY:
{ {
if (!key_lookup.emplace(key).second) { if (!key_lookup.emplace(key).second) {
@ -1098,6 +1284,16 @@ struct PSBTOutput
if (!found_sep) { if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of an output map"); throw std::ios_base::failure("Separator is missing at the end of an output map");
} }
// Make sure required PSBTv2 fields are present
if (m_psbt_version >= 2) {
if (amount == std::nullopt) {
throw std::ios_base::failure("Output amount is required in PSBTv2");
}
if (!script.has_value()) {
throw std::ios_base::failure("Output script is required in PSBTv2");
}
}
} }
template <typename Stream> template <typename Stream>
@ -1113,6 +1309,9 @@ struct PartiallySignedTransaction
// We use a vector of CExtPubKey in the event that there happens to be the same KeyOriginInfos for different CExtPubKeys // We use a vector of CExtPubKey in the event that there happens to be the same KeyOriginInfos for different CExtPubKeys
// Note that this map swaps the key and values from the serialization // Note that this map swaps the key and values from the serialization
std::map<KeyOriginInfo, std::set<CExtPubKey>> m_xpubs; std::map<KeyOriginInfo, std::set<CExtPubKey>> m_xpubs;
std::optional<uint32_t> tx_version;
std::optional<uint32_t> fallback_locktime;
std::optional<std::bitset<8>> m_tx_modifiable;
std::vector<PSBTInput> inputs; std::vector<PSBTInput> inputs;
std::vector<PSBTOutput> outputs; std::vector<PSBTOutput> outputs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
@ -1125,18 +1324,15 @@ struct PartiallySignedTransaction
/** Merge psbt into this. The two psbts must have the same underlying CTransaction (i.e. the /** Merge psbt into this. The two psbts must have the same underlying CTransaction (i.e. the
* same actual Bitcoin transaction.) Returns true if the merge succeeded, false otherwise. */ * same actual Bitcoin transaction.) Returns true if the merge succeeded, false otherwise. */
[[nodiscard]] bool Merge(const PartiallySignedTransaction& psbt); [[nodiscard]] bool Merge(const PartiallySignedTransaction& psbt);
bool AddInput(const CTxIn& txin, PSBTInput& psbtin); bool AddInput(PSBTInput& psbtin);
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout); bool AddOutput(const PSBTOutput& psbtout);
void SetupFromTx(const CMutableTransaction& tx);
void CacheUnsignedTxPieces();
bool ComputeTimeLock(uint32_t& locktime) const;
CMutableTransaction GetUnsignedTx() const;
uint256 GetUniqueID() const;
PartiallySignedTransaction() = default; PartiallySignedTransaction() = default;
explicit PartiallySignedTransaction(const CMutableTransaction& tx); explicit PartiallySignedTransaction(const CMutableTransaction& tx, uint32_t version = 2);
/**
* Finds the UTXO for a given input index
*
* @param[out] utxo The UTXO of the input if found
* @param[in] input_index Index of the input to retrieve the UTXO of
* @return Whether the UTXO for the specified input was found
*/
bool GetInputUTXO(CTxOut& utxo, int input_index) const;
template <typename Stream> template <typename Stream>
inline void Serialize(Stream& s) const { inline void Serialize(Stream& s) const {
@ -1144,11 +1340,13 @@ struct PartiallySignedTransaction
// magic bytes // magic bytes
s << PSBT_MAGIC_BYTES; s << PSBT_MAGIC_BYTES;
// unsigned tx flag if (GetVersion() == 0) {
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX)); // unsigned tx flag
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX));
// Write serialized tx to a stream // Write serialized tx to a stream
SerializeToVector(s, TX_NO_WITNESS(*tx)); SerializeToVector(s, TX_NO_WITNESS(GetUnsignedTx()));
}
// Write xpubs // Write xpubs
for (const auto& xpub_pair : m_xpubs) { for (const auto& xpub_pair : m_xpubs) {
@ -1162,6 +1360,26 @@ struct PartiallySignedTransaction
} }
} }
if (GetVersion() >= 2) {
// Write PSBTv2 tx version, locktime, counts, etc.
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_TX_VERSION));
SerializeToVector(s, *tx_version);
if (fallback_locktime != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_FALLBACK_LOCKTIME));
SerializeToVector(s, *fallback_locktime);
}
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_INPUT_COUNT));
SerializeToVector(s, CompactSizeWriter(inputs.size()));
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_OUTPUT_COUNT));
SerializeToVector(s, CompactSizeWriter(outputs.size()));
if (m_tx_modifiable != std::nullopt) {
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_TX_MODIFIABLE));
SerializeToVector(s, static_cast<uint8_t>(m_tx_modifiable->to_ulong()));
}
}
// PSBT version // PSBT version
if (GetVersion() > 0) { if (GetVersion() > 0) {
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_VERSION)); SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_VERSION));
@ -1211,6 +1429,10 @@ struct PartiallySignedTransaction
// Read global data // Read global data
bool found_sep = false; bool found_sep = false;
uint64_t input_count = 0;
uint64_t output_count = 0;
bool found_input_count = false;
bool found_output_count = false;
while(!s.empty()) { while(!s.empty()) {
// Read // Read
std::vector<unsigned char> key; std::vector<unsigned char> key;
@ -1246,6 +1468,69 @@ struct PartiallySignedTransaction
throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses."); throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses.");
} }
} }
// Set the input and output counts
input_count = tx->vin.size();
output_count = tx->vout.size();
break;
}
case PSBT_GLOBAL_TX_VERSION:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, global transaction version is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global transaction version key is more than one byte type");
}
uint32_t v;
UnserializeFromVector(s, v);
tx_version = v;
break;
}
case PSBT_GLOBAL_FALLBACK_LOCKTIME:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, global fallback locktime is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global fallback locktime key is more than one byte type");
}
uint32_t v;
UnserializeFromVector(s, v);
fallback_locktime = v;
break;
}
case PSBT_GLOBAL_INPUT_COUNT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, global input count is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global input count key is more than one byte type");
}
CompactSizeReader reader(input_count);
UnserializeFromVector(s, reader);
found_input_count = true;
break;
}
case PSBT_GLOBAL_OUTPUT_COUNT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, global output count is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global output count key is more than one byte type");
}
CompactSizeReader reader(output_count);
UnserializeFromVector(s, reader);
found_output_count = true;
break;
}
case PSBT_GLOBAL_TX_MODIFIABLE:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, tx modifiable flags is already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global tx modifiable flags key is more than one byte type");
}
uint8_t tx_mod;
UnserializeFromVector(s, tx_mod);
m_tx_modifiable.emplace(tx_mod);
break; break;
} }
case PSBT_GLOBAL_XPUB: case PSBT_GLOBAL_XPUB:
@ -1324,46 +1609,99 @@ struct PartiallySignedTransaction
throw std::ios_base::failure("Separator is missing at the end of the global map"); throw std::ios_base::failure("Separator is missing at the end of the global map");
} }
// Make sure that we got an unsigned tx const uint32_t psbt_ver = GetVersion();
if (!tx) {
throw std::ios_base::failure("No unsigned transaction was provided"); // Check PSBT version constraints
if (psbt_ver == 0) {
// Make sure that we got an unsigned tx for PSBTv0
if (!tx) {
throw std::ios_base::failure("No unsigned transaction was provided");
}
// Make sure no PSBTv2 fields are present
if (tx_version != std::nullopt) {
throw std::ios_base::failure("PSBT_GLOBAL_TX_VERSION is not allowed in PSBTv0");
}
if (fallback_locktime != std::nullopt) {
throw std::ios_base::failure("PSBT_GLOBAL_FALLBACK_LOCKTIME is not allowed in PSBTv0");
}
if (found_input_count) {
throw std::ios_base::failure("PSBT_GLOBAL_INPUT_COUNT is not allowed in PSBTv0");
}
if (found_output_count) {
throw std::ios_base::failure("PSBT_GLOBAL_OUTPUT_COUNT is not allowed in PSBTv0");
}
if (m_tx_modifiable != std::nullopt) {
throw std::ios_base::failure("PSBT_GLOBAL_TX_MODIFIABLE is not allowed in PSBTv0");
}
}
// Disallow v1
if (psbt_ver == 1) {
throw std::ios_base::failure("There is no PSBT version 1");
}
if (psbt_ver >= 2) {
// Tx version, input, and output counts are required
if (tx_version == std::nullopt) {
throw std::ios_base::failure("PSBT_GLOBAL_TX_VERSION is required in PSBTv2");
}
if (!found_input_count) {
throw std::ios_base::failure("PSBT_GLOBAL_INPUT_COUNT is required in PSBTv2");
}
if (!found_output_count) {
throw std::ios_base::failure("PSBT_GLOBAL_OUTPUT_COUNT is required in PSBTv2");
}
// Unsigned tx is disallowed
if (tx) {
throw std::ios_base::failure("PSBT_GLOBAL_UNSIGNED_TX is not allowed in PSBTv2");
}
} }
// Read input data // Read input data
unsigned int i = 0; unsigned int i = 0;
while (!s.empty() && i < tx->vin.size()) { while (!s.empty() && i < input_count) {
PSBTInput input; PSBTInput input(psbt_ver);
s >> input; s >> input;
inputs.push_back(input); inputs.push_back(input);
// Make sure the non-witness utxo matches the outpoint // Make sure the non-witness utxo matches the outpoint
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
if (input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) { if (psbt_ver == 0) {
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash"); if (input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) {
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
}
if (tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) {
throw std::ios_base::failure("Input specifies output index that does not exist");
}
} }
if (tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { if (psbt_ver >= 2) {
throw std::ios_base::failure("Input specifies output index that does not exist"); if (input.non_witness_utxo->GetHash() != input.prev_txid) {
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
}
if (input.prev_out.value() >= input.non_witness_utxo->vout.size()) {
throw std::ios_base::failure("Input specifies output index that does not exist");
}
} }
} }
++i; ++i;
} }
// Make sure that the number of inputs matches the number of inputs in the transaction // Make sure that the number of inputs matches the number of inputs in the transaction
if (inputs.size() != tx->vin.size()) { if (inputs.size() != input_count) {
throw std::ios_base::failure("Inputs provided does not match the number of inputs in transaction."); throw std::ios_base::failure("Inputs provided does not match the number of inputs in transaction.");
} }
// Read output data // Read output data
i = 0; i = 0;
while (!s.empty() && i < tx->vout.size()) { while (!s.empty() && i < output_count) {
PSBTOutput output; PSBTOutput output(psbt_ver);
s >> output; s >> output;
outputs.push_back(output); outputs.push_back(output);
++i; ++i;
} }
// Make sure that the number of outputs matches the number of outputs in the transaction // Make sure that the number of outputs matches the number of outputs in the transaction
if (outputs.size() != tx->vout.size()) { if (outputs.size() != output_count) {
throw std::ios_base::failure("Outputs provided does not match the number of outputs in transaction."); throw std::ios_base::failure("Outputs provided does not match the number of outputs in transaction.");
} }
CacheUnsignedTxPieces();
} }
template <typename Stream> template <typename Stream>

View file

@ -180,15 +180,15 @@ QString PSBTOperationsDialog::renderTransaction(const PartiallySignedTransaction
QString tx_description; QString tx_description;
QLatin1String bullet_point(" * "); QLatin1String bullet_point(" * ");
CAmount totalAmount = 0; CAmount totalAmount = 0;
for (const CTxOut& out : psbtx.tx->vout) { for (const PSBTOutput& out : psbtx.outputs) {
CTxDestination address; CTxDestination address;
ExtractDestination(out.scriptPubKey, address); ExtractDestination(*out.script, address);
totalAmount += out.nValue; totalAmount += *out.amount;
tx_description.append(bullet_point).append(tr("Sends %1 to %2") tx_description.append(bullet_point).append(tr("Sends %1 to %2")
.arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, out.nValue)) .arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *out.amount))
.arg(QString::fromStdString(EncodeDestination(address)))); .arg(QString::fromStdString(EncodeDestination(address))));
// Check if the address is one of ours // Check if the address is one of ours
if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(out)) tx_description.append(" (" + tr("own address") + ")"); if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(CTxOut(*out.amount, *out.script))) tx_description.append(" (" + tr("own address") + ")");
tx_description.append("<br>"); tx_description.append("<br>");
} }

View file

@ -170,6 +170,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletcreatefundedpsbt", 3, "solving_data"}, { "walletcreatefundedpsbt", 3, "solving_data"},
{ "walletcreatefundedpsbt", 3, "max_tx_weight"}, { "walletcreatefundedpsbt", 3, "max_tx_weight"},
{ "walletcreatefundedpsbt", 4, "bip32derivs" }, { "walletcreatefundedpsbt", 4, "bip32derivs" },
{ "walletcreatefundedpsbt", 5, "psbt_version" },
{ "walletprocesspsbt", 1, "sign" }, { "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" }, { "walletprocesspsbt", 3, "bip32derivs" },
{ "walletprocesspsbt", 4, "finalize" }, { "walletprocesspsbt", 4, "finalize" },
@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createpsbt", 1, "outputs" }, { "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" }, { "createpsbt", 2, "locktime" },
{ "createpsbt", 3, "replaceable" }, { "createpsbt", 3, "replaceable" },
{ "createpsbt", 4, "psbt_version" },
{ "combinepsbt", 0, "txs"}, { "combinepsbt", 0, "txs"},
{ "joinpsbts", 0, "txs"}, { "joinpsbts", 0, "txs"},
{ "finalizepsbt", 1, "extract"}, { "finalizepsbt", 1, "extract"},

View file

@ -182,10 +182,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
// Fetch previous transactions: // Fetch previous transactions:
// First, look in the txindex and the mempool // First, look in the txindex and the mempool
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (PSBTInput& psbt_input : psbtx.inputs) {
PSBTInput& psbt_input = psbtx.inputs.at(i);
const CTxIn& tx_in = psbtx.tx->vin.at(i);
// The `non_witness_utxo` is the whole previous transaction // The `non_witness_utxo` is the whole previous transaction
if (psbt_input.non_witness_utxo) continue; if (psbt_input.non_witness_utxo) continue;
@ -194,29 +191,26 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
// Look in the txindex // Look in the txindex
if (g_txindex) { if (g_txindex) {
uint256 block_hash; uint256 block_hash;
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx); g_txindex->FindTx(psbt_input.prev_txid, block_hash, tx);
} }
// If we still don't have it look in the mempool // If we still don't have it look in the mempool
if (!tx) { if (!tx) {
tx = node.mempool->get(tx_in.prevout.hash); tx = node.mempool->get(psbt_input.prev_txid);
} }
if (tx) { if (tx) {
psbt_input.non_witness_utxo = tx; psbt_input.non_witness_utxo = tx;
} else { } else {
coins[tx_in.prevout]; // Create empty map entry keyed by prevout coins[psbt_input.GetOutPoint()]; // Create empty map entry keyed by prevout
} }
} }
// If we still haven't found all of the inputs, look for the missing ones in the utxo set // If we still haven't found all of the inputs, look for the missing ones in the utxo set
if (!coins.empty()) { if (!coins.empty()) {
FindCoins(node, coins); FindCoins(node, coins);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (PSBTInput& input : psbtx.inputs) {
PSBTInput& input = psbtx.inputs.at(i);
// If there are still missing utxos, add them if they were found in the utxo set // If there are still missing utxos, add them if they were found in the utxo set
if (!input.non_witness_utxo) { if (!input.non_witness_utxo) {
const CTxIn& tx_in = psbtx.tx->vin.at(i); const Coin& coin = coins.at(input.GetOutPoint());
const Coin& coin = coins.at(tx_in.prevout);
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) { if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
input.witness_utxo = coin.out; input.witness_utxo = coin.out;
} }
@ -226,7 +220,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
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.inputs.size(); ++i) {
if (PSBTInputSigned(psbtx.inputs.at(i))) { if (PSBTInputSigned(psbtx.inputs.at(i))) {
continue; continue;
} }
@ -239,7 +233,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
} }
// Update script/keypath information using descriptor data. // Update script/keypath information using descriptor data.
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
UpdatePSBTOutput(provider, psbtx, i); UpdatePSBTOutput(provider, psbtx, i);
} }
@ -879,6 +873,11 @@ const RPCResult decodepsbt_inputs{
{ {
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
}}, }},
{RPCResult::Type::STR_HEX, "previous_txid", /*optional=*/ true, "TXID of the transaction containing the output being spent by this input."},
{RPCResult::Type::NUM, "previous_vout", /* optional=*/ true, "Index of the output being spent"},
{RPCResult::Type::NUM, "sequence", /* optional=*/ true, "Sequence number for this inputs"},
{RPCResult::Type::NUM, "time_locktime", /* optional=*/ true, "Required time-based locktime for this input"},
{RPCResult::Type::NUM, "height_locktime", /* optional=*/ true, "Required height-based locktime for this input"},
{RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"}, {RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"},
{RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "", {RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "",
{ {
@ -991,6 +990,10 @@ const RPCResult decodepsbt_outputs{
{RPCResult::Type::STR, "path", "The path"}, {RPCResult::Type::STR, "path", "The path"},
}}, }},
}}, }},
{RPCResult::Type::NUM, "amount", /* optional=*/ true, "The amount (nValue) for this output"},
{RPCResult::Type::OBJ, "script", /* optional=*/ true, "The output script (scriptPubKey) for this output",
{{RPCResult::Type::ELISION, "", "The layout is the same as the output of scriptPubKeys in decoderawtransaction."}},
},
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
{RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order", {RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order",
{ {
@ -1054,7 +1057,7 @@ static RPCHelpMan decodepsbt()
RPCResult{ RPCResult{
RPCResult::Type::OBJ, "", "", RPCResult::Type::OBJ, "", "",
{ {
{RPCResult::Type::OBJ, "tx", "The decoded network-serialized unsigned transaction.", {RPCResult::Type::OBJ, "tx", /*optional=*/true, "The decoded network-serialized unsigned transaction.",
{ {
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."}, {RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
}}, }},
@ -1067,7 +1070,14 @@ static RPCHelpMan decodepsbt()
{RPCResult::Type::STR, "path", "The path"}, {RPCResult::Type::STR, "path", "The path"},
}}, }},
}}, }},
{RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"}, {RPCResult::Type::NUM, "tx_version", /* optional */ true, "The version number of the unsigned transaction. Not to be confused with PSBT version"},
{RPCResult::Type::NUM, "fallback_locktime", /* optional */ true, "The locktime to fallback to if no inputs specify a required locktime."},
{RPCResult::Type::NUM, "input_count", /* optional */ true, "The number of inputs in this psbt"},
{RPCResult::Type::NUM, "output_count", /* optional */ true, "The number of outputs in this psbt."},
{RPCResult::Type::BOOL, "inputs_modifiable", /* optional */ true, "Whether inputs can be modified"},
{RPCResult::Type::BOOL, "outputs_modifiable", /* optional */ true, "Whether outputs can be modified"},
{RPCResult::Type::BOOL, "has_sighash_single", /* optional */ true, "Whether this PSBT has SIGHASH_SINGLE inputs"},
{RPCResult::Type::NUM, "psbt_version", /* optional */ true, "The PSBT version number. Not to be confused with the unsigned transaction version"},
{RPCResult::Type::ARR, "proprietary", "The global proprietary map", {RPCResult::Type::ARR, "proprietary", "The global proprietary map",
{ {
{RPCResult::Type::OBJ, "", "", {RPCResult::Type::OBJ, "", "",
@ -1101,10 +1111,12 @@ static RPCHelpMan decodepsbt()
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
// Add the decoded tx if (psbtx.tx != std::nullopt) {
UniValue tx_univ(UniValue::VOBJ); // Add the decoded tx
TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false); UniValue tx_univ(UniValue::VOBJ);
result.pushKV("tx", std::move(tx_univ)); TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
result.pushKV("tx", std::move(tx_univ));
}
// Add the global xpubs // Add the global xpubs
UniValue global_xpubs(UniValue::VARR); UniValue global_xpubs(UniValue::VARR);
@ -1123,6 +1135,23 @@ static RPCHelpMan decodepsbt()
} }
result.pushKV("global_xpubs", std::move(global_xpubs)); result.pushKV("global_xpubs", std::move(global_xpubs));
// Add PSBTv2 stuff
if (psbtx.GetVersion() == 2) {
if (psbtx.tx_version != std::nullopt) {
result.pushKV("tx_version", *psbtx.tx_version);
}
if (psbtx.fallback_locktime != std::nullopt) {
result.pushKV("fallback_locktime", static_cast<uint64_t>(*psbtx.fallback_locktime));
}
result.pushKV("input_count", (uint64_t)psbtx.inputs.size());
result.pushKV("output_count", (uint64_t)psbtx.outputs.size());
if (psbtx.m_tx_modifiable != std::nullopt) {
result.pushKV("inputs_modifiable", psbtx.m_tx_modifiable->test(0));
result.pushKV("outputs_modifiable", psbtx.m_tx_modifiable->test(1));
result.pushKV("has_sighash_single", psbtx.m_tx_modifiable->test(2));
}
}
// PSBT version // PSBT version
result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion())); result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion()));
@ -1170,7 +1199,7 @@ static RPCHelpMan decodepsbt()
have_a_utxo = true; have_a_utxo = true;
} }
if (input.non_witness_utxo) { if (input.non_witness_utxo) {
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n]; txout = input.non_witness_utxo->vout[*input.prev_out];
UniValue non_wit(UniValue::VOBJ); UniValue non_wit(UniValue::VOBJ);
TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false); TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
@ -1280,6 +1309,25 @@ static RPCHelpMan decodepsbt()
in.pushKV("hash256_preimages", std::move(hash256_preimages)); in.pushKV("hash256_preimages", std::move(hash256_preimages));
} }
// PSBTv2
if (psbtx.GetVersion() == 2) {
if (!input.prev_txid.IsNull()) {
in.pushKV("previous_txid", input.prev_txid.GetHex());
}
if (input.prev_out != std::nullopt) {
in.pushKV("previous_vout", static_cast<uint64_t>(*input.prev_out));
}
if (input.sequence != std::nullopt) {
in.pushKV("sequence", static_cast<uint64_t>(*input.sequence));
}
if (input.time_locktime != std::nullopt) {
in.pushKV("time_locktime", static_cast<uint64_t>(*input.time_locktime));
}
if (input.height_locktime!= std::nullopt) {
in.pushKV("height_locktime", static_cast<uint64_t>(*input.height_locktime));
}
}
// Taproot key path signature // Taproot key path signature
if (!input.m_tap_key_sig.empty()) { if (!input.m_tap_key_sig.empty()) {
in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig)); in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig));
@ -1450,6 +1498,18 @@ static RPCHelpMan decodepsbt()
out.pushKV("bip32_derivs", std::move(keypaths)); out.pushKV("bip32_derivs", std::move(keypaths));
} }
// PSBTv2 stuff
if (psbtx.GetVersion() == 2) {
if (output.amount != std::nullopt) {
out.pushKV("amount", ValueFromAmount(*output.amount));
}
if (output.script.has_value()) {
UniValue spk(UniValue::VOBJ);
ScriptToUniv(*output.script, spk, /*include_hex=*/true, /*include_address=*/true);
out.pushKV("script", spk);
}
}
// Taproot internal key // Taproot internal key
if (!output.m_tap_internal_key.IsNull()) { if (!output.m_tap_internal_key.IsNull()) {
out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key)); out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key));
@ -1529,8 +1589,8 @@ static RPCHelpMan decodepsbt()
outputs.push_back(std::move(out)); outputs.push_back(std::move(out));
// Fee calculation // Fee calculation
if (MoneyRange(psbtx.tx->vout[i].nValue) && MoneyRange(output_value + psbtx.tx->vout[i].nValue)) { if (MoneyRange(*output.amount) && MoneyRange(output_value + *output.amount)) {
output_value += psbtx.tx->vout[i].nValue; output_value += *output.amount;
} else { } else {
// Hack to just not show fee later // Hack to just not show fee later
have_all_utxos = false; have_all_utxos = false;
@ -1657,7 +1717,12 @@ static RPCHelpMan createpsbt()
"Implements the Creator role.\n" "Implements the Creator role.\n"
"Note that the transaction's inputs are not signed, and\n" "Note that the transaction's inputs are not signed, and\n"
"it is not stored in the wallet or transmitted to the network.\n", "it is not stored in the wallet or transmitted to the network.\n",
CreateTxDoc(), Cat<std::vector<RPCArg>>(
CreateTxDoc(),
{
{"psbt_version", RPCArg::Type::NUM, RPCArg::Default{2}, "The PSBT version number to use."},
}
),
RPCResult{ RPCResult{
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)" RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
}, },
@ -1666,7 +1731,6 @@ static RPCHelpMan createpsbt()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{ {
std::optional<bool> rbf; std::optional<bool> rbf;
if (!request.params[3].isNull()) { if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool(); rbf = request.params[3].get_bool();
@ -1674,15 +1738,16 @@ static RPCHelpMan createpsbt()
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
// Make a blank psbt // Make a blank psbt
PartiallySignedTransaction psbtx; uint32_t psbt_version = 2;
psbtx.tx = rawTx; if (!request.params[4].isNull()) {
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { psbt_version = request.params[4].getInt<int>();
psbtx.inputs.emplace_back();
} }
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { if (psbt_version != 2 && psbt_version != 0) {
psbtx.outputs.emplace_back(); throw JSONRPCError(RPC_INVALID_PARAMETER, "The PSBT version can only be 2 or 0");
} }
PartiallySignedTransaction psbtx(rawTx, psbt_version);
// Serialize the PSBT // Serialize the PSBT
DataStream ssTx{}; DataStream ssTx{};
ssTx << psbtx; ssTx << psbtx;
@ -1741,14 +1806,7 @@ static RPCHelpMan converttopsbt()
} }
// Make a blank psbt // Make a blank psbt
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx(tx, /*version=*/2);
psbtx.tx = tx;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.emplace_back();
}
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.emplace_back();
}
// Serialize the PSBT // Serialize the PSBT
DataStream ssTx{}; DataStream ssTx{};
@ -1840,32 +1898,38 @@ static RPCHelpMan joinpsbts()
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) { if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
} }
if (psbtx.GetVersion() != 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "joinpsbts only operates on version 0 PSBTs");
}
psbtxs.push_back(psbtx); psbtxs.push_back(psbtx);
// Choose the highest version number // Choose the highest version number
if (psbtx.tx->version > best_version) { if (*psbtx.tx_version > best_version) {
best_version = psbtx.tx->version; best_version = *psbtx.tx_version;
} }
// Choose the lowest lock time // Choose the lowest lock time
if (psbtx.tx->nLockTime < best_locktime) { if (*psbtx.fallback_locktime < best_locktime) {
best_locktime = psbtx.tx->nLockTime; best_locktime = *psbtx.fallback_locktime;
} }
} }
// Create a blank psbt where everything will be added // Create a blank psbt where everything will be added
PartiallySignedTransaction merged_psbt; PartiallySignedTransaction merged_psbt;
merged_psbt.tx_version = best_version;
merged_psbt.fallback_locktime = best_locktime;
// TODO: Remove for PSBTv2
merged_psbt.tx = CMutableTransaction(); merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->version = best_version; merged_psbt.tx->version = best_version;
merged_psbt.tx->nLockTime = best_locktime; merged_psbt.tx->nLockTime = best_locktime;
// Merge // Merge
for (auto& psbt : psbtxs) { for (auto& psbt : psbtxs) {
for (unsigned int i = 0; i < psbt.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbt.inputs.size(); ++i) {
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) { if (!merged_psbt.AddInput(psbt.inputs[i])) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.tx->vin[i].prevout.hash.ToString(), psbt.tx->vin[i].prevout.n)); throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.inputs[i].prev_txid.ToString(), *psbt.inputs[i].prev_out));
} }
} }
for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) { for (unsigned int i = 0; i < psbt.outputs.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); merged_psbt.AddOutput(psbt.outputs[i]);
} }
for (auto& xpub_pair : psbt.m_xpubs) { for (auto& xpub_pair : psbt.m_xpubs) {
if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) { if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) {
@ -1888,14 +1952,17 @@ static RPCHelpMan joinpsbts()
std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext()); std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt; PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx_version = merged_psbt.tx_version;
shuffled_psbt.fallback_locktime = merged_psbt.fallback_locktime;
// TODO: Remove for PSBTv2
shuffled_psbt.tx = CMutableTransaction(); shuffled_psbt.tx = CMutableTransaction();
shuffled_psbt.tx->version = merged_psbt.tx->version; shuffled_psbt.tx->version = merged_psbt.tx->version;
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime; shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
for (int i : input_indices) { for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]); shuffled_psbt.AddInput(merged_psbt.inputs[i]);
} }
for (int i : output_indices) { for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]); shuffled_psbt.AddOutput(merged_psbt.outputs[i]);
} }
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end()); shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());

View file

@ -607,6 +607,19 @@ struct ChronoFormatter {
template <typename U> template <typename U>
using LossyChronoFormatter = ChronoFormatter<U, true>; using LossyChronoFormatter = ChronoFormatter<U, true>;
class CompactSizeReader
{
protected:
uint64_t& n;
public:
explicit CompactSizeReader(uint64_t& n_in) : n(n_in) {}
template<typename Stream>
void Unserialize(Stream &s) const {
n = ReadCompactSize<Stream>(s);
}
};
class CompactSizeWriter class CompactSizeWriter
{ {
protected: protected:

View file

@ -73,6 +73,7 @@ add_executable(test_bitcoin
pool_tests.cpp pool_tests.cpp
pow_tests.cpp pow_tests.cpp
prevector_tests.cpp prevector_tests.cpp
psbt_tests.cpp
raii_event_tests.cpp raii_event_tests.cpp
random_tests.cpp random_tests.cpp
rbf_tests.cpp rbf_tests.cpp

View file

@ -197,11 +197,11 @@ FUZZ_TARGET_DESERIALIZE(prefilled_transaction_deserialize, {
DeserializeFromFuzzingInput(buffer, prefilled_transaction); DeserializeFromFuzzingInput(buffer, prefilled_transaction);
}) })
FUZZ_TARGET_DESERIALIZE(psbt_input_deserialize, { FUZZ_TARGET_DESERIALIZE(psbt_input_deserialize, {
PSBTInput psbt_input; PSBTInput psbt_input(0);
DeserializeFromFuzzingInput(buffer, psbt_input); DeserializeFromFuzzingInput(buffer, psbt_input);
}) })
FUZZ_TARGET_DESERIALIZE(psbt_output_deserialize, { FUZZ_TARGET_DESERIALIZE(psbt_output_deserialize, {
PSBTOutput psbt_output; PSBTOutput psbt_output(0);
DeserializeFromFuzzingInput(buffer, psbt_output); DeserializeFromFuzzingInput(buffer, psbt_output);
}) })
FUZZ_TARGET_DESERIALIZE(block_deserialize, { FUZZ_TARGET_DESERIALIZE(block_deserialize, {

View file

@ -59,7 +59,7 @@ FUZZ_TARGET(psbt)
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) { for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
CTxOut tx_out; CTxOut tx_out;
if (psbt.GetInputUTXO(tx_out, i)) { if (psbt.inputs.at(i).GetUTXO(tx_out)) {
(void)tx_out.IsNull(); (void)tx_out.IsNull();
(void)tx_out.ToString(); (void)tx_out.ToString();
} }
@ -84,11 +84,11 @@ FUZZ_TARGET(psbt)
psbt_mut = psbt; psbt_mut = psbt;
(void)CombinePSBTs(psbt_mut, {psbt_mut, psbt_merge}); (void)CombinePSBTs(psbt_mut, {psbt_mut, psbt_merge});
psbt_mut = psbt; psbt_mut = psbt;
for (unsigned int i = 0; i < psbt_merge.tx->vin.size(); ++i) { for (auto& psbt_in : psbt_merge.inputs) {
(void)psbt_mut.AddInput(psbt_merge.tx->vin[i], psbt_merge.inputs[i]); (void)psbt_mut.AddInput(psbt_in);
} }
for (unsigned int i = 0; i < psbt_merge.tx->vout.size(); ++i) { for (const auto& psbt_out : psbt_merge.outputs) {
Assert(psbt_mut.AddOutput(psbt_merge.tx->vout[i], psbt_merge.outputs[i])); Assert(psbt_mut.AddOutput(psbt_out));
} }
psbt_mut.unknown.insert(psbt_merge.unknown.begin(), psbt_merge.unknown.end()); psbt_mut.unknown.insert(psbt_merge.unknown.begin(), psbt_merge.unknown.end());
} }

43
src/test/psbt_tests.cpp Normal file
View file

@ -0,0 +1,43 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
#include <psbt.h>
#include <boost/test/unit_test.hpp>
#include <test/util/setup_common.h>
BOOST_FIXTURE_TEST_SUITE(psbt_tests, BasicTestingSetup)
void CheckTimeLock(const std::string& base64_psbt, std::optional<uint32_t> timelock)
{
PartiallySignedTransaction psbt;
std::string error;
bool decoded = DecodeBase64PSBT(psbt, base64_psbt, error);
BOOST_CHECK_MESSAGE(decoded, error);
uint32_t computed_timelock;
bool computed = psbt.ComputeTimeLock(computed_timelock);
if (timelock) {
BOOST_CHECK(computed);
BOOST_CHECK(computed_timelock == timelock);
} else {
BOOST_CHECK(!computed);
}
}
BOOST_AUTO_TEST_CASE(psbt2_timelock_test)
{
CheckTimeLock("cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", 0);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 0);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", 10000);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 10000);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 1657048460);
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", std::nullopt);
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -95,7 +95,7 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx) static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
{ {
// Make a blank psbt // Make a blank psbt
PartiallySignedTransaction psbtx(rawTx); PartiallySignedTransaction psbtx(rawTx, /*version=*/2);
// 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.
@ -1173,7 +1173,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
result.pushKV("txid", txid.GetHex()); result.pushKV("txid", txid.GetHex());
} else { } else {
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx, /*version=*/2);
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, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true)};
CHECK_NONFATAL(!err); CHECK_NONFATAL(!err);
@ -1725,6 +1725,7 @@ RPCHelpMan walletcreatefundedpsbt()
FundTxDoc()), FundTxDoc()),
RPCArgOptions{.oneline_description="options"}}, RPCArgOptions{.oneline_description="options"}},
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
{"psbt_version", RPCArg::Type::NUM, RPCArg::Default(2), "The PSBT version number to use."},
}, },
RPCResult{ RPCResult{
RPCResult::Type::OBJ, "", "", RPCResult::Type::OBJ, "", "",
@ -1772,7 +1773,15 @@ RPCHelpMan walletcreatefundedpsbt()
auto txr = FundTransaction(wallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/true); auto txr = FundTransaction(wallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/true);
// Make a blank psbt // Make a blank psbt
PartiallySignedTransaction psbtx(CMutableTransaction(*txr.tx)); uint32_t psbt_version = 2;
if (!request.params[5].isNull()) {
psbt_version = request.params[5].getInt<int>();
}
if (psbt_version != 2 && psbt_version != 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "The PSBT version can only be 2 or 0");
}
PartiallySignedTransaction psbtx(CMutableTransaction(*txr.tx), psbt_version);
// 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();

View file

@ -643,8 +643,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
} }
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i); PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) { if (PSBTInputSigned(input)) {
@ -658,7 +657,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
// 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 (*input.prev_out >= input.non_witness_utxo->vout.size()) {
return PSBTError::MISSING_INPUTS; return PSBTError::MISSING_INPUTS;
} }
} else if (input.witness_utxo.IsNull()) { } else if (input.witness_utxo.IsNull()) {
@ -677,7 +676,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
} }
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i); UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i);
} }
@ -2570,8 +2569,7 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
if (n_signed) { if (n_signed) {
*n_signed = 0; *n_signed = 0;
} }
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i); PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) { if (PSBTInputSigned(input)) {
@ -2588,10 +2586,10 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
if (!input.witness_utxo.IsNull()) { if (!input.witness_utxo.IsNull()) {
script = input.witness_utxo.scriptPubKey; script = input.witness_utxo.scriptPubKey;
} else if (input.non_witness_utxo) { } else if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) { if (*input.prev_out >= input.non_witness_utxo->vout.size()) {
return PSBTError::MISSING_INPUTS; return PSBTError::MISSING_INPUTS;
} }
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey; script = input.non_witness_utxo->vout[*input.prev_out].scriptPubKey;
} else { } else {
// There's no UTXO so we can just skip this now // There's no UTXO so we can just skip this now
continue; continue;
@ -2652,8 +2650,8 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
} }
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
std::unique_ptr<SigningProvider> keys = GetSolvingProvider(psbtx.tx->vout.at(i).scriptPubKey); std::unique_ptr<SigningProvider> keys = GetSolvingProvider(*psbtx.outputs.at(i).script);
if (!keys) { if (!keys) {
continue; continue;
} }

View file

@ -75,6 +75,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Mutate the transaction so that one of the inputs is invalid // Mutate the transaction so that one of the inputs is invalid
psbtx.tx->vin[0].prevout.n = 2; psbtx.tx->vin[0].prevout.n = 2;
psbtx.inputs[0].prev_out = 2;
// Try to sign the mutated input // Try to sign the mutated input
SignatureData sigdata; SignatureData sigdata;

View file

@ -2218,17 +2218,14 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
} }
LOCK(cs_wallet); LOCK(cs_wallet);
// Get all of the previous transactions // Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { for (PSBTInput& input : psbtx.inputs) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) { if (PSBTInputSigned(input)) {
continue; continue;
} }
// If we have no utxo, grab it from the wallet. // If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo) { if (!input.non_witness_utxo) {
const uint256& txhash = txin.prevout.hash; const uint256& txhash = input.prev_txid;
const auto it = mapWallet.find(txhash); const auto it = mapWallet.find(txhash);
if (it != mapWallet.end()) { if (it != mapWallet.end()) {
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;

View file

@ -40,7 +40,29 @@
"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<",
"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"
], ],
"invalid_with_msg": [ "invalid_with_msg": [
[ [
@ -126,7 +148,21 @@
"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"
], ],
"creator" : [ "creator" : [
{ {
@ -148,6 +184,7 @@
"bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1 "bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1
} }
], ],
"version": 0,
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA="
} }
], ],
@ -210,10 +247,10 @@
}, },
{ {
"combine" : [ "combine" : [
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=", "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=",
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
], ],
"result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8KDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" "result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8K8AECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCvABAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwrwAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
} }
], ],
"finalizer" : [ "finalizer" : [

View file

@ -32,6 +32,7 @@ from test_framework.psbt import (
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,
PSBT_OUT_SCRIPT,
) )
from test_framework.script import CScript, OP_TRUE from test_framework.script import CScript, OP_TRUE
from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
@ -85,9 +86,7 @@ class PSBTTest(BitcoinTestFramework):
# Modify the raw transaction by changing the output address, so the signature is no longer valid # Modify the raw transaction by changing the output address, so the signature is no longer valid
signed_psbt_obj = PSBT.from_base64(signed_psbt) signed_psbt_obj = PSBT.from_base64(signed_psbt)
substitute_addr = wallet.getnewaddress() signed_psbt_obj.o[0].map[PSBT_OUT_SCRIPT] = CScript([OP_TRUE])
raw = wallet.createrawtransaction([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{substitute_addr: 0.9999}])
signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)
# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete # Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False) signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
@ -159,11 +158,11 @@ class PSBTTest(BitcoinTestFramework):
psbtx1 = wallet.walletcreatefundedpsbt([], {target_address: 0.1}, 0, {'fee_rate': 1, 'maxconf': 0})['psbt'] psbtx1 = wallet.walletcreatefundedpsbt([], {target_address: 0.1}, 0, {'fee_rate': 1, 'maxconf': 0})['psbt']
# Make sure we only had the one input # Make sure we only had the one input
tx1_inputs = self.nodes[0].decodepsbt(psbtx1)['tx']['vin'] tx1_inputs = self.nodes[0].decodepsbt(psbtx1)['inputs']
assert_equal(len(tx1_inputs), 1) assert_equal(len(tx1_inputs), 1)
utxo1 = tx1_inputs[0] utxo1 = tx1_inputs[0]
assert_equal(unconfirmed_txid, utxo1['txid']) assert_equal(unconfirmed_txid, utxo1['previous_txid'])
signed_tx1 = wallet.walletprocesspsbt(psbtx1) signed_tx1 = wallet.walletprocesspsbt(psbtx1)
txid1 = self.nodes[0].sendrawtransaction(signed_tx1['hex']) txid1 = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
@ -172,23 +171,23 @@ class PSBTTest(BitcoinTestFramework):
assert txid1 in mempool assert txid1 in mempool
self.log.info("Fail to craft a new PSBT that sends more funds with add_inputs = False") self.log.info("Fail to craft a new PSBT that sends more funds with add_inputs = False")
assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. Please allow other inputs to be automatically selected or include more coins manually", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': False}) assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. Please allow other inputs to be automatically selected or include more coins manually", wallet.walletcreatefundedpsbt, [{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': False})
self.log.info("Fail to craft a new PSBT with minconf above highest one") self.log.info("Fail to craft a new PSBT with minconf above highest one")
assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 3, 'fee_rate': 10}) assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 3, 'fee_rate': 10})
self.log.info("Fail to broadcast a new PSBT with maxconf 0 due to BIP125 rules to verify it actually chose unconfirmed outputs") self.log.info("Fail to broadcast a new PSBT with maxconf 0 due to BIP125 rules to verify it actually chose unconfirmed outputs")
psbt_invalid = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'maxconf': 0, 'fee_rate': 10})['psbt'] psbt_invalid = wallet.walletcreatefundedpsbt([{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'maxconf': 0, 'fee_rate': 10})['psbt']
signed_invalid = wallet.walletprocesspsbt(psbt_invalid) signed_invalid = wallet.walletprocesspsbt(psbt_invalid)
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, signed_invalid['hex']) assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, signed_invalid['hex'])
self.log.info("Craft a replacement adding inputs with highest confs possible") self.log.info("Craft a replacement adding inputs with highest confs possible")
psbtx2 = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 2, 'fee_rate': 10})['psbt'] psbtx2 = wallet.walletcreatefundedpsbt([{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 2, 'fee_rate': 10})['psbt']
tx2_inputs = self.nodes[0].decodepsbt(psbtx2)['tx']['vin'] tx2_inputs = self.nodes[0].decodepsbt(psbtx2)['inputs']
assert_greater_than_or_equal(len(tx2_inputs), 2) assert_greater_than_or_equal(len(tx2_inputs), 2)
for vin in tx2_inputs: for vin in tx2_inputs:
if vin['txid'] != unconfirmed_txid: if vin['previous_txid'] != unconfirmed_txid:
assert_greater_than_or_equal(self.nodes[0].gettxout(vin['txid'], vin['vout'])['confirmations'], 2) assert_greater_than_or_equal(self.nodes[0].gettxout(vin['previous_txid'], vin['previous_vout'])['confirmations'], 2)
signed_tx2 = wallet.walletprocesspsbt(psbtx2) signed_tx2 = wallet.walletprocesspsbt(psbtx2)
txid2 = self.nodes[0].sendrawtransaction(signed_tx2['hex']) txid2 = self.nodes[0].sendrawtransaction(signed_tx2['hex'])
@ -205,7 +204,7 @@ class PSBTTest(BitcoinTestFramework):
# The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node # The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node
decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"]) decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"])
changepos = psbtx["changepos"] changepos = psbtx["changepos"]
assert_equal(decoded_psbt["tx"]["vout"][changepos]["scriptPubKey"]["type"], expected_type) assert_equal(decoded_psbt["outputs"][changepos]["script"]["type"], expected_type)
def run_test(self): def run_test(self):
# Create and fund a raw tx for sending 10 BTC # Create and fund a raw tx for sending 10 BTC
@ -247,7 +246,9 @@ class PSBTTest(BitcoinTestFramework):
max_tx_weight_sufficient = 1000 # 1k vbytes is enough max_tx_weight_sufficient = 1000 # 1k vbytes is enough
psbt = self.nodes[0].walletcreatefundedpsbt(outputs=dest_arg,locktime=0, options={"max_tx_weight": max_tx_weight_sufficient})["psbt"] psbt = self.nodes[0].walletcreatefundedpsbt(outputs=dest_arg,locktime=0, options={"max_tx_weight": max_tx_weight_sufficient})["psbt"]
weight = self.nodes[0].decodepsbt(psbt)["tx"]["weight"] psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
final_tx = self.nodes[0].finalizepsbt(psbt)["hex"]
weight = self.nodes[0].decoderawtransaction(final_tx)["weight"]
# ensure the transaction's weight is below the specified max_tx_weight. # ensure the transaction's weight is below the specified max_tx_weight.
assert_greater_than_or_equal(max_tx_weight_sufficient, weight) assert_greater_than_or_equal(max_tx_weight_sufficient, weight)
@ -258,7 +259,7 @@ class PSBTTest(BitcoinTestFramework):
self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}) self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90})
psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}, 0, {"add_inputs": True})['psbt'] psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}, 0, {"add_inputs": True})['psbt']
assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['tx']['vin']), 2) assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['inputs']), 2)
# Inputs argument can be null # Inputs argument can be null
self.nodes[0].walletcreatefundedpsbt(None, {self.nodes[2].getnewaddress():10}) self.nodes[0].walletcreatefundedpsbt(None, {self.nodes[2].getnewaddress():10})
@ -488,15 +489,20 @@ class PSBTTest(BitcoinTestFramework):
# Create a psbt spending outputs from nodes 1 and 2 # Create a psbt spending outputs from nodes 1 and 2
psbt_orig = self.nodes[0].createpsbt([utxo1, utxo2], {self.nodes[0].getnewaddress():25.999}) psbt_orig = self.nodes[0].createpsbt([utxo1, utxo2], {self.nodes[0].getnewaddress():25.999})
# Check that the default psbt version is 2
assert_equal(self.nodes[0].decodepsbt(psbt_orig)["psbt_version"], 2)
# Update psbts, should only have data for one input and not the other # Update psbts, should only have data for one input and not the other
psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig, False, "ALL")['psbt'] psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig, False, "ALL")['psbt']
psbt1_decoded = self.nodes[0].decodepsbt(psbt1) psbt1_decoded = self.nodes[0].decodepsbt(psbt1)
assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1] assert len(psbt1_decoded['inputs'][0].keys()) > 3
assert len(psbt1_decoded['inputs'][1].keys()) == 3
# Check that BIP32 path was added # Check that BIP32 path was added
assert "bip32_derivs" in psbt1_decoded['inputs'][0] assert "bip32_derivs" in psbt1_decoded['inputs'][0]
psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig, False, "ALL", False)['psbt'] psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig, False, "ALL", False)['psbt']
psbt2_decoded = self.nodes[0].decodepsbt(psbt2) psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1] assert len(psbt2_decoded['inputs'][0].keys()) == 3
assert len(psbt2_decoded['inputs'][1].keys()) > 3
# Check that BIP32 paths were not added # Check that BIP32 paths were not added
assert "bip32_derivs" not in psbt2_decoded['inputs'][1] assert "bip32_derivs" not in psbt2_decoded['inputs'][1]
@ -518,33 +524,33 @@ class PSBTTest(BitcoinTestFramework):
unspent = self.nodes[0].listunspent()[0] unspent = self.nodes[0].listunspent()[0]
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False, "add_inputs": True}, False) psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False, "add_inputs": True}, False)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): for psbt_in in decoded_psbt["inputs"]:
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_greater_than(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
assert "bip32_derivs" not in psbt_in assert "bip32_derivs" not in psbt_in
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2) assert_equal(decoded_psbt["fallback_locktime"], block_height+2)
# Same construction with only locktime set and RBF explicitly enabled # Same construction with only locktime set and RBF explicitly enabled
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True, "add_inputs": True}, True) psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True, "add_inputs": True}, True)
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): for psbt_in in decoded_psbt["inputs"]:
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_equal(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
assert "bip32_derivs" in psbt_in assert "bip32_derivs" in psbt_in
assert_equal(decoded_psbt["tx"]["locktime"], block_height) assert_equal(decoded_psbt["fallback_locktime"], block_height)
# Same construction without optional arguments # Same construction without optional arguments
psbtx_info = self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}]) psbtx_info = self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"]) decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): for psbt_in in decoded_psbt["inputs"]:
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_equal(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
assert "bip32_derivs" in psbt_in assert "bip32_derivs" in psbt_in
assert_equal(decoded_psbt["tx"]["locktime"], 0) assert_equal(decoded_psbt["fallback_locktime"], 0)
# Same construction without optional arguments, for a node with -walletrbf=0 # Same construction without optional arguments, for a node with -walletrbf=0
unspent1 = self.nodes[1].listunspent()[0] unspent1 = self.nodes[1].listunspent()[0]
psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height, {"add_inputs": True}) psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height, {"add_inputs": True})
decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"]) decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"])
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]): for psbt_in in decoded_psbt["inputs"]:
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE) assert_greater_than(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
assert "bip32_derivs" in psbt_in assert "bip32_derivs" in psbt_in
# Make sure change address wallet does not have P2SH innerscript access to results in success # Make sure change address wallet does not have P2SH innerscript access to results in success
@ -585,7 +591,7 @@ class PSBTTest(BitcoinTestFramework):
# BIP 174 Test Vectors # BIP 174 Test Vectors
# Check that unknown values are just passed through # Check that unknown values are just passed through
unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=" unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA="
unknown_out = self.nodes[0].walletprocesspsbt(unknown_psbt)['psbt'] unknown_out = self.nodes[0].walletprocesspsbt(unknown_psbt)['psbt']
assert_equal(unknown_psbt, unknown_out) assert_equal(unknown_psbt, unknown_out)
@ -614,7 +620,7 @@ class PSBTTest(BitcoinTestFramework):
# Creator Tests # Creator Tests
for creator in creators: for creator in creators:
created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False) created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False, psbt_version=creator['version'])
assert_equal(created_tx, creator['result']) assert_equal(created_tx, creator['result'])
# Signer tests # Signer tests
@ -668,54 +674,66 @@ class PSBTTest(BitcoinTestFramework):
utxo1, utxo2, utxo3 = self.create_outpoints(self.nodes[1], outputs=[{addr1: 11}, {addr2: 11}, {addr3: 11}]) utxo1, utxo2, utxo3 = self.create_outpoints(self.nodes[1], outputs=[{addr1: 11}, {addr2: 11}, {addr3: 11}])
self.sync_all() self.sync_all()
psbt_v2_required_keys = ["previous_vout", "sequence", "previous_txid"]
def test_psbt_input_keys(psbt_input, keys): def test_psbt_input_keys(psbt_input, keys):
"""Check that the psbt input has only the expected keys.""" """Check that the psbt input has only the expected keys."""
keys.extend(["previous_vout", "sequence", "previous_txid"])
assert_equal(set(keys), set(psbt_input.keys())) assert_equal(set(keys), set(psbt_input.keys()))
# Create a PSBT. None of the inputs are filled initially # Create a PSBT. None of the inputs are filled initially
psbt = self.nodes[1].createpsbt([utxo1, utxo2, utxo3], {self.nodes[0].getnewaddress():32.999}) psbt = self.nodes[1].createpsbt([utxo1, utxo2, utxo3], {self.nodes[0].getnewaddress():32.999})
decoded = self.nodes[1].decodepsbt(psbt) decoded = self.nodes[1].decodepsbt(psbt)
test_psbt_input_keys(decoded['inputs'][0], []) test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys)
test_psbt_input_keys(decoded['inputs'][1], []) test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys)
test_psbt_input_keys(decoded['inputs'][2], []) test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys)
# Update a PSBT with UTXOs from the node # Update a PSBT with UTXOs from the node
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness # Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
updated = self.nodes[1].utxoupdatepsbt(psbt) updated = self.nodes[1].utxoupdatepsbt(psbt)
decoded = self.nodes[1].decodepsbt(updated) decoded = self.nodes[1].decodepsbt(updated)
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo']) test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo'])
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo']) test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo'])
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo']) test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo'])
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in # Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]] descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs) updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
decoded = self.nodes[1].decodepsbt(updated) decoded = self.nodes[1].decodepsbt(updated)
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'bip32_derivs']) test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo', 'bip32_derivs']) test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo', 'bip32_derivs'])
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script']) test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo', 'witness_utxo', 'bip32_derivs', 'redeem_script'])
# Cannot join PSBTv2s
psbt1 = self.nodes[1].createpsbt(inputs=[utxo1], outputs={self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=0)
psbt2 = self.nodes[1].createpsbt(inputs=[utxo1], outputs={self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=2)
assert_raises_rpc_error(-8, "joinpsbts only operates on version 0 PSBTs", self.nodes[1].joinpsbts, [psbt1, psbt2])
# Two PSBTs with a common input should not be joinable # Two PSBTs with a common input should not be joinable
psbt1 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')}) psbt2 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=0)
assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated]) assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, psbt2])
# Join two distinct PSBTs # Join two distinct PSBTs
psbt1 = self.nodes[1].createpsbt(inputs=[utxo1, utxo2, utxo3], outputs={self.nodes[0].getnewaddress():32.999}, psbt_version=0)
addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit") addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit")
utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0] utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0]
self.generate(self.nodes[0], 6) self.generate(self.nodes[0], 6)
psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')}) psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')}, psbt_version=0)
psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt'] psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt']
psbt2_decoded = self.nodes[0].decodepsbt(psbt2) psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0] assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0]
joined = self.nodes[0].joinpsbts([psbt, psbt2]) joined = self.nodes[0].joinpsbts([psbt1, psbt2])
joined_decoded = self.nodes[0].decodepsbt(joined) joined_decoded = self.nodes[0].decodepsbt(joined)
assert len(joined_decoded['inputs']) == 4 and len(joined_decoded['outputs']) == 2 and "final_scriptwitness" not in joined_decoded['inputs'][3] and "final_scriptSig" not in joined_decoded['inputs'][3] assert_equal(len(joined_decoded['inputs']), 4)
assert_equal(len(joined_decoded['outputs']), 2)
assert "final_scriptwitness" not in joined_decoded['inputs'][3]
assert "final_scriptSig" not in joined_decoded['inputs'][3]
# Check that joining shuffles the inputs and outputs # Check that joining shuffles the inputs and outputs
# 10 attempts should be enough to get a shuffled join # 10 attempts should be enough to get a shuffled join
shuffled = False shuffled = False
for _ in range(10): for _ in range(10):
shuffled_joined = self.nodes[0].joinpsbts([psbt, psbt2]) shuffled_joined = self.nodes[0].joinpsbts([psbt1, psbt2])
shuffled |= joined != shuffled_joined shuffled |= joined != shuffled_joined
if shuffled: if shuffled:
break break
@ -802,11 +820,9 @@ class PSBTTest(BitcoinTestFramework):
final = signed['hex'] final = signed['hex']
dec = self.nodes[0].decodepsbt(signed["psbt"]) dec = self.nodes[0].decodepsbt(signed["psbt"])
for i, txin in enumerate(dec["tx"]["vin"]): for psbt_in in dec["inputs"]:
if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:
input_idx = i
break break
psbt_in = dec["inputs"][input_idx]
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else "" scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex) input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)

View file

@ -4,10 +4,14 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
import base64 import base64
import struct
from io import BytesIO
from .messages import ( from .messages import (
CTransaction, CTransaction,
deser_string, deser_string,
deser_compact_size,
from_binary, from_binary,
ser_compact_size, ser_compact_size,
) )
@ -100,37 +104,78 @@ class PSBT:
self.g = g if g is not None else PSBTMap() self.g = g if g is not None else PSBTMap()
self.i = i if i is not None else [] self.i = i if i is not None else []
self.o = o if o is not None else [] self.o = o if o is not None else []
self.tx = None self.in_count = len(i) if i is not None else None
self.out_count = len(o) if o is not None else None
self.version = None
def deserialize(self, f): def deserialize(self, f):
assert f.read(5) == b"psbt\xff" assert f.read(5) == b"psbt\xff"
self.g = from_binary(PSBTMap, f) self.g = from_binary(PSBTMap, f)
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX]) self.version = 0
self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin] if PSBT_GLOBAL_VERSION in self.g.map:
self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout] assert PSBT_GLOBAL_INPUT_COUNT in self.g.map
assert PSBT_GLOBAL_OUTPUT_COUNT in self.g.map
self.version = struct.unpack("<I", self.g.map[PSBT_GLOBAL_VERSION])[0]
assert self.version in [0, 2]
if self.version == 2:
self.in_count = deser_compact_size(BytesIO(self.g.map[PSBT_GLOBAL_INPUT_COUNT]))
self.out_count = deser_compact_size(BytesIO(self.g.map[PSBT_GLOBAL_OUTPUT_COUNT]))
else:
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
self.in_count = len(tx.vin)
self.out_count = len(tx.vout)
self.i = [from_binary(PSBTMap, f) for _ in range(self.in_count)]
self.o = [from_binary(PSBTMap, f) for _ in range(self.out_count)]
return self return self
def serialize(self): def serialize(self):
assert isinstance(self.g, PSBTMap) assert isinstance(self.g, PSBTMap)
assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i) assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o) assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map if self.version is not None and self.version == 2:
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX]) self.g.map[PSBT_GLOBAL_INPUT_COUNT] = ser_compact_size(len(self.i))
assert len(tx.vin) == len(self.i) self.g.map[PSBT_GLOBAL_OUTPUT_COUNT] = ser_compact_size(len(self.o))
assert len(tx.vout) == len(self.o)
psbt = [x.serialize() for x in [self.g] + self.i + self.o] psbt = [x.serialize() for x in [self.g] + self.i + self.o]
return b"psbt\xff" + b"".join(psbt) return b"psbt\xff" + b"".join(psbt)
def make_blank(self): def make_blank(self):
""" """
Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX Remove all fields except for required fields depending on version
""" """
for m in self.i + self.o: if self.version == 0:
m.map.clear() for m in self.i + self.o:
m.map.clear()
self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]}) self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
elif self.version == 2:
self.g = PSBTMap(map={
PSBT_GLOBAL_TX_VERSION: self.g.map[PSBT_GLOBAL_TX_VERSION],
PSBT_GLOBAL_INPUT_COUNT: self.g.map[PSBT_GLOBAL_INPUT_COUNT],
PSBT_GLOBAL_OUTPUT_COUNT: self.g.map[PSBT_GLOBAL_OUTPUT_COUNT],
PSBT_GLOBAL_VERSION: self.g.map[PSBT_GLOBAL_VERSION],
})
new_i = []
for m in self.i:
new_i.append(PSBTMap(map={
PSBT_IN_PREVIOUS_TXID: m.map[PSBT_IN_PREVIOUS_TXID],
PSBT_IN_OUTPUT_INDEX: m.map[PSBT_IN_OUTPUT_INDEX],
}))
self.i = new_i
new_o = []
for m in self.o:
new_o.append(PSBTMap(map={
PSBT_OUT_SCRIPT: m.map[PSBT_OUT_SCRIPT],
PSBT_OUT_AMOUNT: m.map[PSBT_OUT_AMOUNT],
}))
self.o = new_o
else:
assert False
def to_base64(self): def to_base64(self):
return base64.b64encode(self.serialize()).decode("utf8") return base64.b64encode(self.serialize()).decode("utf8")

View file

@ -633,14 +633,14 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
{"fee_rate": 1, "add_inputs": False}, True)['psbt'] {"fee_rate": 1, "add_inputs": False}, True)['psbt']
psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True) psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)
original_txid = watcher.sendrawtransaction(psbt_signed["hex"]) original_txid = watcher.sendrawtransaction(psbt_signed["hex"])
assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1) assert_equal(len(watcher.decodepsbt(psbt)["inputs"]), 1)
# bumpfee can't be used on watchonly wallets # bumpfee can't be used on watchonly wallets
assert_raises_rpc_error(-4, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.", watcher.bumpfee, original_txid) assert_raises_rpc_error(-4, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.", watcher.bumpfee, original_txid)
# Bump fee, obnoxiously high to add additional watchonly input # Bump fee, obnoxiously high to add additional watchonly input
bumped_psbt = watcher.psbtbumpfee(original_txid, fee_rate=HIGH) bumped_psbt = watcher.psbtbumpfee(original_txid, fee_rate=HIGH)
assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1) assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["inputs"]), 1)
assert "txid" not in bumped_psbt assert "txid" not in bumped_psbt
assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"]) assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"])
assert not watcher.finalizepsbt(bumped_psbt["psbt"])["complete"] assert not watcher.finalizepsbt(bumped_psbt["psbt"])["complete"]

View file

@ -1171,10 +1171,10 @@ class RawTransactionsTest(BitcoinTestFramework):
tx = wallet.send(outputs=[{addr1: 8}], **options) tx = wallet.send(outputs=[{addr1: 8}], **options)
assert tx["complete"] assert tx["complete"]
# Check that only the preset inputs were added to the tx # Check that only the preset inputs were added to the tx
decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin'] decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])["inputs"]
assert_equal(len(decoded_psbt_inputs), 2) assert_equal(len(decoded_psbt_inputs), 2)
for input in decoded_psbt_inputs: for input in decoded_psbt_inputs:
assert_equal(input["txid"], source_tx["txid"]) assert_equal(input["previous_txid"], source_tx["txid"])
# Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true # Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true
options = {"add_inputs": True, "add_to_wallet": True} options = {"add_inputs": True, "add_to_wallet": True}
@ -1213,10 +1213,10 @@ class RawTransactionsTest(BitcoinTestFramework):
}) })
psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, **options) psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, **options)
# Check that only the preset inputs were added to the tx # Check that only the preset inputs were added to the tx
decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin'] decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])["inputs"]
assert_equal(len(decoded_psbt_inputs), 2) assert_equal(len(decoded_psbt_inputs), 2)
for input in decoded_psbt_inputs: for input in decoded_psbt_inputs:
assert_equal(input["txid"], source_tx["txid"]) assert_equal(input["previous_txid"], source_tx["txid"])
# Case (5), 'walletcreatefundedpsbt' command # Case (5), 'walletcreatefundedpsbt' command
# Explicit add_inputs=true, no preset inputs # Explicit add_inputs=true, no preset inputs

View file

@ -35,13 +35,13 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
@staticmethod @staticmethod
def _check_psbt(psbt, to, value, multisig): def _check_psbt(psbt, to, value, multisig):
"""Helper function for any of the N participants to check the psbt with decodepsbt and verify it is OK before signing.""" """Helper function for any of the N participants to check the psbt with decodepsbt and verify it is OK before signing."""
tx = multisig.decodepsbt(psbt)["tx"] decoded = multisig.decodepsbt(psbt)
amount = 0 amount = 0
for vout in tx["vout"]: for psbt_out in decoded["outputs"]:
address = vout["scriptPubKey"]["address"] address = psbt_out["script"]["address"]
assert_equal(multisig.getaddressinfo(address)["ischange"], address != to) assert_equal(multisig.getaddressinfo(address)["ischange"], address != to)
if address == to: if address == to:
amount += vout["value"] amount += psbt_out["amount"]
assert_approx(amount, float(value), vspan=0.001) assert_approx(amount, float(value), vspan=0.001)
def participants_create_multisigs(self, external_xpubs, internal_xpubs): def participants_create_multisigs(self, external_xpubs, internal_xpubs):

View file

@ -380,10 +380,10 @@ class WalletSendTest(BitcoinTestFramework):
assert res["complete"] assert res["complete"]
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0) res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0)
assert res["complete"] assert res["complete"]
assert_equal(self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"], change_address) assert_equal(self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"], change_address)
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0) res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0)
assert res["complete"] assert res["complete"]
change_address = self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"] change_address = self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"]
assert change_address[0] == "m" or change_address[0] == "n" assert change_address[0] == "m" or change_address[0] == "n"
self.log.info("Set lock time...") self.log.info("Set lock time...")
@ -483,11 +483,9 @@ class WalletSendTest(BitcoinTestFramework):
assert signed["complete"] assert signed["complete"]
dec = self.nodes[0].decodepsbt(signed["psbt"]) dec = self.nodes[0].decodepsbt(signed["psbt"])
for i, txin in enumerate(dec["tx"]["vin"]): for psbt_in in dec["inputs"]:
if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:
input_idx = i
break break
psbt_in = dec["inputs"][input_idx]
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else "" scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex) input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)

View file

@ -306,9 +306,9 @@ class SendallTest(BitcoinTestFramework):
decoded = self.nodes[0].decodepsbt(psbt) decoded = self.nodes[0].decodepsbt(psbt)
assert_equal(len(decoded["inputs"]), 1) assert_equal(len(decoded["inputs"]), 1)
assert_equal(len(decoded["outputs"]), 1) assert_equal(len(decoded["outputs"]), 1)
assert_equal(decoded["tx"]["vin"][0]["txid"], utxo["txid"]) assert_equal(decoded["inputs"][0]["previous_txid"], utxo["txid"])
assert_equal(decoded["tx"]["vin"][0]["vout"], utxo["vout"]) assert_equal(decoded["inputs"][0]["previous_vout"], utxo["vout"])
assert_equal(decoded["tx"]["vout"][0]["scriptPubKey"]["address"], self.remainder_target) assert_equal(decoded["outputs"][0]["script"]["address"], self.remainder_target)
@cleanup @cleanup
def sendall_with_minconf(self): def sendall_with_minconf(self):