mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-15 12:19:46 +01:00
Merge 1137807299
into a50af6e4c4
This commit is contained in:
commit
bb81a46601
25 changed files with 1049 additions and 249 deletions
|
@ -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"")
|
||||||
|
|
6
doc/release-notes-21283.md
Normal file
6
doc/release-notes-21283.md
Normal 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.
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
283
src/psbt.cpp
283
src/psbt.cpp
|
@ -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,14 +49,98 @@ 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
|
||||||
{
|
{
|
||||||
|
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()) {
|
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
|
||||||
|
// Prevent duplicate inputs
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
tx->vin.push_back(txin);
|
tx->vin.push_back(txin);
|
||||||
|
@ -59,33 +151,127 @@ bool PartiallySignedTransaction::AddInput(const CTxIn& txin, PSBTInput& psbtin)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput& psbtout)
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx != std::nullopt) {
|
||||||
|
// This is a v0 psbt, do the v0 AddOutput
|
||||||
|
CTxOut txout(*psbtout.amount, *psbtout.script);
|
||||||
tx->vout.push_back(txout);
|
tx->vout.push_back(txout);
|
||||||
outputs.push_back(psbtout);
|
outputs.push_back(psbtout);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
|
// 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);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
382
src/psbt.h
382
src/psbt.h
|
@ -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;
|
||||||
|
@ -56,6 +68,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;
|
||||||
|
@ -70,7 +84,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
|
||||||
|
@ -209,6 +223,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;
|
||||||
|
@ -221,11 +241,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 {
|
||||||
|
@ -350,6 +380,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;
|
||||||
|
@ -572,6 +627,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) {
|
||||||
|
@ -702,6 +831,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>
|
||||||
|
@ -716,17 +855,24 @@ 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<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 {
|
||||||
|
@ -745,6 +891,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;
|
||||||
|
@ -841,6 +999,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) {
|
||||||
|
@ -938,6 +1124,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>
|
||||||
|
@ -953,6 +1149,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;
|
||||||
|
@ -965,18 +1164,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 {
|
||||||
|
@ -984,11 +1180,13 @@ struct PartiallySignedTransaction
|
||||||
// magic bytes
|
// magic bytes
|
||||||
s << PSBT_MAGIC_BYTES;
|
s << PSBT_MAGIC_BYTES;
|
||||||
|
|
||||||
|
if (GetVersion() == 0) {
|
||||||
// unsigned tx flag
|
// unsigned tx flag
|
||||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX));
|
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) {
|
||||||
|
@ -1002,6 +1200,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));
|
||||||
|
@ -1051,6 +1269,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;
|
||||||
|
@ -1086,6 +1308,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:
|
||||||
|
@ -1164,20 +1449,62 @@ 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();
|
||||||
|
|
||||||
|
// Check PSBT version constraints
|
||||||
|
if (psbt_ver == 0) {
|
||||||
|
// Make sure that we got an unsigned tx for PSBTv0
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
throw std::ios_base::failure("No unsigned transaction was provided");
|
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 (psbt_ver == 0) {
|
||||||
if (input.non_witness_utxo->GetHash() != tx->vin[i].prevout.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");
|
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
|
||||||
}
|
}
|
||||||
|
@ -1185,25 +1512,36 @@ struct PartiallySignedTransaction
|
||||||
throw std::ios_base::failure("Input specifies output index that does not exist");
|
throw std::ios_base::failure("Input specifies output index that does not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (psbt_ver >= 2) {
|
||||||
|
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>
|
||||||
|
|
|
@ -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>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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, "",
|
||||||
{
|
{
|
||||||
|
@ -960,6 +959,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",
|
||||||
{
|
{
|
||||||
|
@ -1012,7 +1015,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."},
|
||||||
}},
|
}},
|
||||||
|
@ -1025,7 +1028,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, "", "",
|
||||||
|
@ -1059,10 +1069,12 @@ static RPCHelpMan decodepsbt()
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
|
|
||||||
|
if (psbtx.tx != std::nullopt) {
|
||||||
// Add the decoded tx
|
// Add the decoded tx
|
||||||
UniValue tx_univ(UniValue::VOBJ);
|
UniValue tx_univ(UniValue::VOBJ);
|
||||||
TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
|
TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
|
||||||
result.pushKV("tx", std::move(tx_univ));
|
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);
|
||||||
|
@ -1081,6 +1093,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()));
|
||||||
|
|
||||||
|
@ -1128,7 +1157,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);
|
||||||
|
@ -1238,6 +1267,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));
|
||||||
|
@ -1362,6 +1410,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));
|
||||||
|
@ -1425,8 +1485,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;
|
||||||
|
@ -1551,7 +1611,12 @@ static RPCHelpMan createpsbt()
|
||||||
return RPCHelpMan{"createpsbt",
|
return RPCHelpMan{"createpsbt",
|
||||||
"\nCreates a transaction in the Partially Signed Transaction format.\n"
|
"\nCreates a transaction in the Partially Signed Transaction format.\n"
|
||||||
"Implements the Creator role.\n",
|
"Implements the Creator role.\n",
|
||||||
|
Cat<std::vector<RPCArg>>(
|
||||||
CreateTxDoc(),
|
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)"
|
||||||
},
|
},
|
||||||
|
@ -1560,7 +1625,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();
|
||||||
|
@ -1568,15 +1632,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;
|
||||||
|
@ -1635,14 +1700,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{};
|
||||||
|
@ -1734,32 +1792,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) {
|
||||||
|
@ -1782,14 +1846,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());
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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, {
|
||||||
|
|
|
@ -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
43
src/test/psbt_tests.cpp
Normal 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()
|
|
@ -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.
|
||||||
|
@ -1167,7 +1167,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);
|
||||||
|
@ -1719,6 +1719,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, "", "",
|
||||||
|
@ -1764,7 +1765,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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,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;
|
||||||
|
|
|
@ -2215,17 +2215,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;
|
||||||
|
|
|
@ -40,7 +40,29 @@
|
||||||
"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<",
|
||||||
|
"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"
|
||||||
],
|
],
|
||||||
"invalid_with_msg": [
|
"invalid_with_msg": [
|
||||||
[
|
[
|
||||||
|
@ -72,7 +94,21 @@
|
||||||
"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"
|
||||||
],
|
],
|
||||||
"creator" : [
|
"creator" : [
|
||||||
{
|
{
|
||||||
|
@ -94,6 +130,7 @@
|
||||||
"bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1
|
"bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"version": 0,
|
||||||
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA="
|
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA="
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -156,10 +193,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" : [
|
||||||
|
|
|
@ -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
|
||||||
|
@ -87,9 +88,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)
|
||||||
|
@ -161,11 +160,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'])
|
||||||
|
@ -174,23 +173,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'])
|
||||||
|
@ -207,7 +206,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
|
||||||
|
@ -249,7 +248,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)
|
||||||
|
|
||||||
|
@ -260,7 +261,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})
|
||||||
|
@ -494,15 +495,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]
|
||||||
|
|
||||||
|
@ -524,33 +530,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
|
||||||
|
@ -591,7 +597,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)
|
||||||
|
|
||||||
|
@ -620,7 +626,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
|
||||||
|
@ -675,54 +681,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
|
||||||
|
@ -812,11 +830,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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
self.version = 0
|
||||||
|
if PSBT_GLOBAL_VERSION in self.g.map:
|
||||||
|
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
|
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
|
||||||
self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
|
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
|
||||||
self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
|
self.in_count = len(tx.vin)
|
||||||
self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
|
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
|
||||||
"""
|
"""
|
||||||
|
if self.version == 0:
|
||||||
for m in self.i + self.o:
|
for m in self.i + self.o:
|
||||||
m.map.clear()
|
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")
|
||||||
|
|
|
@ -640,14 +640,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"]
|
||||||
|
|
|
@ -1179,10 +1179,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}
|
||||||
|
@ -1221,10 +1221,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
|
||||||
|
|
|
@ -39,13 +39,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):
|
||||||
|
|
|
@ -431,10 +431,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...")
|
||||||
|
@ -537,11 +537,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)
|
||||||
|
|
|
@ -313,9 +313,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):
|
||||||
|
|
Loading…
Add table
Reference in a new issue