mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 06:43:45 +01:00
6c7a17a8e0psbt: support externally provided preimages for Miniscript satisfaction (Antoine Poinsot)840a396029qa: add a "smart" Miniscript fuzz target (Antoine Poinsot)17e3547241qa: add a fuzz target generating random nodes from a binary encoding (Antoine Poinsot)611e12502aqa: functional test Miniscript signing with key and timelocks (Antoine Poinsot)d57b7f2021refactor: make descriptors in Miniscript functional test more readable (Antoine Poinsot)0a8fc9e200wallet: check solvability using descriptor in AvailableCoins (Antoine Poinsot)560e62b1e2script/sign: signing support for Miniscripts with hash preimage challenges (Antoine Poinsot)a2f81b6a8fscript/sign: signing support for Miniscript with timelocks (Antoine Poinsot)61c6d1a844script/sign: basic signing support for Miniscript descriptors (Antoine Poinsot)4242c1c521Align 'e' property of or_d and andor with website spec (Pieter Wuille)f5deb41780Various additional explanations of the satisfaction logic from Pieter (Pieter Wuille)22c5b00345miniscript: satisfaction support (Antoine Poinsot) Pull request description: This makes the Miniscript descriptors solvable. Note this introduces signing support for much more complex scripts than the wallet was previously able to solve, and the whole tooling isn't provided for a complete Miniscript integration in the wallet. Particularly, the PSBT<->Miniscript integration isn't entirely covered in this PR. ACKs for top commit: achow101: ACK6c7a17a8e0sipa: utACK6c7a17a8e0(to the extent that it's not my own code). Tree-SHA512: a71ec002aaf66bd429012caa338fc58384067bcd2f453a46e21d381ed1bacc8e57afb9db57c0fb4bf40de43b30808815e9ebc0ae1fbd9e61df0e7b91a17771cc
534 lines
20 KiB
C++
534 lines
20 KiB
C++
// Copyright (c) 2009-2022 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <psbt.h>
|
|
|
|
#include <policy/policy.h>
|
|
#include <util/check.h>
|
|
#include <util/strencodings.h>
|
|
|
|
|
|
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
|
|
{
|
|
inputs.resize(tx.vin.size());
|
|
outputs.resize(tx.vout.size());
|
|
}
|
|
|
|
bool PartiallySignedTransaction::IsNull() const
|
|
{
|
|
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
|
|
}
|
|
|
|
bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
|
|
{
|
|
// Prohibited to merge two PSBTs over different transactions
|
|
if (tx->GetHash() != psbt.tx->GetHash()) {
|
|
return false;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < inputs.size(); ++i) {
|
|
inputs[i].Merge(psbt.inputs[i]);
|
|
}
|
|
for (unsigned int i = 0; i < outputs.size(); ++i) {
|
|
outputs[i].Merge(psbt.outputs[i]);
|
|
}
|
|
for (auto& xpub_pair : psbt.m_xpubs) {
|
|
if (m_xpubs.count(xpub_pair.first) == 0) {
|
|
m_xpubs[xpub_pair.first] = xpub_pair.second;
|
|
} else {
|
|
m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
|
|
}
|
|
}
|
|
unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool PartiallySignedTransaction::AddInput(const CTxIn& txin, PSBTInput& psbtin)
|
|
{
|
|
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
|
|
return false;
|
|
}
|
|
tx->vin.push_back(txin);
|
|
psbtin.partial_sigs.clear();
|
|
psbtin.final_script_sig.clear();
|
|
psbtin.final_script_witness.SetNull();
|
|
inputs.push_back(psbtin);
|
|
return true;
|
|
}
|
|
|
|
bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput& psbtout)
|
|
{
|
|
tx->vout.push_back(txout);
|
|
outputs.push_back(psbtout);
|
|
return true;
|
|
}
|
|
|
|
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
|
|
{
|
|
const PSBTInput& input = inputs[input_index];
|
|
uint32_t prevout_index = tx->vin[input_index].prevout.n;
|
|
if (input.non_witness_utxo) {
|
|
if (prevout_index >= input.non_witness_utxo->vout.size()) {
|
|
return false;
|
|
}
|
|
if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) {
|
|
return false;
|
|
}
|
|
utxo = input.non_witness_utxo->vout[prevout_index];
|
|
} else if (!input.witness_utxo.IsNull()) {
|
|
utxo = input.witness_utxo;
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void PSBTInput::FillSignatureData(SignatureData& sigdata) const
|
|
{
|
|
if (!final_script_sig.empty()) {
|
|
sigdata.scriptSig = final_script_sig;
|
|
sigdata.complete = true;
|
|
}
|
|
if (!final_script_witness.IsNull()) {
|
|
sigdata.scriptWitness = final_script_witness;
|
|
sigdata.complete = true;
|
|
}
|
|
if (sigdata.complete) {
|
|
return;
|
|
}
|
|
|
|
sigdata.signatures.insert(partial_sigs.begin(), partial_sigs.end());
|
|
if (!redeem_script.empty()) {
|
|
sigdata.redeem_script = redeem_script;
|
|
}
|
|
if (!witness_script.empty()) {
|
|
sigdata.witness_script = witness_script;
|
|
}
|
|
for (const auto& key_pair : hd_keypaths) {
|
|
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
|
|
}
|
|
if (!m_tap_key_sig.empty()) {
|
|
sigdata.taproot_key_path_sig = m_tap_key_sig;
|
|
}
|
|
for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) {
|
|
sigdata.taproot_script_sigs.emplace(pubkey_leaf, sig);
|
|
}
|
|
if (!m_tap_internal_key.IsNull()) {
|
|
sigdata.tr_spenddata.internal_key = m_tap_internal_key;
|
|
}
|
|
if (!m_tap_merkle_root.IsNull()) {
|
|
sigdata.tr_spenddata.merkle_root = m_tap_merkle_root;
|
|
}
|
|
for (const auto& [leaf_script, control_block] : m_tap_scripts) {
|
|
sigdata.tr_spenddata.scripts.emplace(leaf_script, control_block);
|
|
}
|
|
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
|
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
|
}
|
|
for (const auto& [hash, preimage] : ripemd160_preimages) {
|
|
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
|
}
|
|
for (const auto& [hash, preimage] : sha256_preimages) {
|
|
sigdata.sha256_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
|
}
|
|
for (const auto& [hash, preimage] : hash160_preimages) {
|
|
sigdata.hash160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
|
}
|
|
for (const auto& [hash, preimage] : hash256_preimages) {
|
|
sigdata.hash256_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
|
|
}
|
|
}
|
|
|
|
void PSBTInput::FromSignatureData(const SignatureData& sigdata)
|
|
{
|
|
if (sigdata.complete) {
|
|
partial_sigs.clear();
|
|
hd_keypaths.clear();
|
|
redeem_script.clear();
|
|
witness_script.clear();
|
|
|
|
if (!sigdata.scriptSig.empty()) {
|
|
final_script_sig = sigdata.scriptSig;
|
|
}
|
|
if (!sigdata.scriptWitness.IsNull()) {
|
|
final_script_witness = sigdata.scriptWitness;
|
|
}
|
|
return;
|
|
}
|
|
|
|
partial_sigs.insert(sigdata.signatures.begin(), sigdata.signatures.end());
|
|
if (redeem_script.empty() && !sigdata.redeem_script.empty()) {
|
|
redeem_script = sigdata.redeem_script;
|
|
}
|
|
if (witness_script.empty() && !sigdata.witness_script.empty()) {
|
|
witness_script = sigdata.witness_script;
|
|
}
|
|
for (const auto& entry : sigdata.misc_pubkeys) {
|
|
hd_keypaths.emplace(entry.second);
|
|
}
|
|
if (!sigdata.taproot_key_path_sig.empty()) {
|
|
m_tap_key_sig = sigdata.taproot_key_path_sig;
|
|
}
|
|
for (const auto& [pubkey_leaf, sig] : sigdata.taproot_script_sigs) {
|
|
m_tap_script_sigs.emplace(pubkey_leaf, sig);
|
|
}
|
|
if (!sigdata.tr_spenddata.internal_key.IsNull()) {
|
|
m_tap_internal_key = sigdata.tr_spenddata.internal_key;
|
|
}
|
|
if (!sigdata.tr_spenddata.merkle_root.IsNull()) {
|
|
m_tap_merkle_root = sigdata.tr_spenddata.merkle_root;
|
|
}
|
|
for (const auto& [leaf_script, control_block] : sigdata.tr_spenddata.scripts) {
|
|
m_tap_scripts.emplace(leaf_script, control_block);
|
|
}
|
|
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
|
|
m_tap_bip32_paths.emplace(pubkey, leaf_origin);
|
|
}
|
|
}
|
|
|
|
void PSBTInput::Merge(const PSBTInput& input)
|
|
{
|
|
if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo;
|
|
if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) {
|
|
witness_utxo = input.witness_utxo;
|
|
}
|
|
|
|
partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end());
|
|
ripemd160_preimages.insert(input.ripemd160_preimages.begin(), input.ripemd160_preimages.end());
|
|
sha256_preimages.insert(input.sha256_preimages.begin(), input.sha256_preimages.end());
|
|
hash160_preimages.insert(input.hash160_preimages.begin(), input.hash160_preimages.end());
|
|
hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end());
|
|
hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end());
|
|
unknown.insert(input.unknown.begin(), input.unknown.end());
|
|
m_tap_script_sigs.insert(input.m_tap_script_sigs.begin(), input.m_tap_script_sigs.end());
|
|
m_tap_scripts.insert(input.m_tap_scripts.begin(), input.m_tap_scripts.end());
|
|
m_tap_bip32_paths.insert(input.m_tap_bip32_paths.begin(), input.m_tap_bip32_paths.end());
|
|
|
|
if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script;
|
|
if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script;
|
|
if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig;
|
|
if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness;
|
|
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_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root;
|
|
}
|
|
|
|
void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
|
|
{
|
|
if (!redeem_script.empty()) {
|
|
sigdata.redeem_script = redeem_script;
|
|
}
|
|
if (!witness_script.empty()) {
|
|
sigdata.witness_script = witness_script;
|
|
}
|
|
for (const auto& key_pair : hd_keypaths) {
|
|
sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair);
|
|
}
|
|
if (!m_tap_tree.empty() && m_tap_internal_key.IsFullyValid()) {
|
|
TaprootBuilder builder;
|
|
for (const auto& [depth, leaf_ver, script] : m_tap_tree) {
|
|
builder.Add((int)depth, script, (int)leaf_ver, /*track=*/true);
|
|
}
|
|
assert(builder.IsComplete());
|
|
builder.Finalize(m_tap_internal_key);
|
|
TaprootSpendData spenddata = builder.GetSpendData();
|
|
|
|
sigdata.tr_spenddata.internal_key = m_tap_internal_key;
|
|
sigdata.tr_spenddata.Merge(spenddata);
|
|
}
|
|
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
|
|
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
|
|
}
|
|
}
|
|
|
|
void PSBTOutput::FromSignatureData(const SignatureData& sigdata)
|
|
{
|
|
if (redeem_script.empty() && !sigdata.redeem_script.empty()) {
|
|
redeem_script = sigdata.redeem_script;
|
|
}
|
|
if (witness_script.empty() && !sigdata.witness_script.empty()) {
|
|
witness_script = sigdata.witness_script;
|
|
}
|
|
for (const auto& entry : sigdata.misc_pubkeys) {
|
|
hd_keypaths.emplace(entry.second);
|
|
}
|
|
if (!sigdata.tr_spenddata.internal_key.IsNull()) {
|
|
m_tap_internal_key = sigdata.tr_spenddata.internal_key;
|
|
}
|
|
if (sigdata.tr_builder.has_value() && sigdata.tr_builder->HasScripts()) {
|
|
m_tap_tree = sigdata.tr_builder->GetTreeTuples();
|
|
}
|
|
for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) {
|
|
m_tap_bip32_paths.emplace(pubkey, leaf_origin);
|
|
}
|
|
}
|
|
|
|
bool PSBTOutput::IsNull() const
|
|
{
|
|
return redeem_script.empty() && witness_script.empty() && hd_keypaths.empty() && unknown.empty();
|
|
}
|
|
|
|
void PSBTOutput::Merge(const PSBTOutput& output)
|
|
{
|
|
hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.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());
|
|
|
|
if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script;
|
|
if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script;
|
|
if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key;
|
|
if (m_tap_tree.empty() && !output.m_tap_tree.empty()) m_tap_tree = output.m_tap_tree;
|
|
}
|
|
|
|
bool PSBTInputSigned(const PSBTInput& input)
|
|
{
|
|
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
|
|
}
|
|
|
|
bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned int input_index, const PrecomputedTransactionData* txdata)
|
|
{
|
|
CTxOut utxo;
|
|
assert(psbt.inputs.size() >= input_index);
|
|
const PSBTInput& input = psbt.inputs[input_index];
|
|
|
|
if (input.non_witness_utxo) {
|
|
// 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;
|
|
if (prevout.n >= input.non_witness_utxo->vout.size()) {
|
|
return false;
|
|
}
|
|
if (input.non_witness_utxo->GetHash() != prevout.hash) {
|
|
return false;
|
|
}
|
|
utxo = input.non_witness_utxo->vout[prevout.n];
|
|
} else if (!input.witness_utxo.IsNull()) {
|
|
utxo = input.witness_utxo;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
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});
|
|
} 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});
|
|
}
|
|
}
|
|
|
|
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
|
|
size_t count = 0;
|
|
for (const auto& input : psbt.inputs) {
|
|
if (!PSBTInputSigned(input)) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index)
|
|
{
|
|
CMutableTransaction& tx = *Assert(psbt.tx);
|
|
const CTxOut& out = tx.vout.at(index);
|
|
PSBTOutput& psbt_out = psbt.outputs.at(index);
|
|
|
|
// Fill a SignatureData with output info
|
|
SignatureData sigdata;
|
|
psbt_out.FillSignatureData(sigdata);
|
|
|
|
// Construct a would-be spend of this output, to update sigdata with.
|
|
// Note that ProduceSignature is used to fill in metadata (not actual signatures),
|
|
// so provider does not need to provide any private keys (it can be a HidingSigningProvider).
|
|
MutableTransactionSignatureCreator creator(tx, /*input_idx=*/0, out.nValue, SIGHASH_ALL);
|
|
ProduceSignature(provider, creator, out.scriptPubKey, sigdata);
|
|
|
|
// Put redeem_script, witness_script, key paths, into PSBTOutput.
|
|
psbt_out.FromSignatureData(sigdata);
|
|
}
|
|
|
|
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
|
|
{
|
|
const CMutableTransaction& tx = *psbt.tx;
|
|
bool have_all_spent_outputs = true;
|
|
std::vector<CTxOut> utxos(tx.vin.size());
|
|
for (size_t idx = 0; idx < tx.vin.size(); ++idx) {
|
|
if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false;
|
|
}
|
|
PrecomputedTransactionData txdata;
|
|
if (have_all_spent_outputs) {
|
|
txdata.Init(tx, std::move(utxos), true);
|
|
} else {
|
|
txdata.Init(tx, {}, true);
|
|
}
|
|
return txdata;
|
|
}
|
|
|
|
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);
|
|
const CMutableTransaction& tx = *psbt.tx;
|
|
|
|
if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
|
|
return true;
|
|
}
|
|
|
|
// Fill SignatureData with input info
|
|
SignatureData sigdata;
|
|
input.FillSignatureData(sigdata);
|
|
|
|
// Get UTXO
|
|
bool require_witness_sig = false;
|
|
CTxOut utxo;
|
|
|
|
if (input.non_witness_utxo) {
|
|
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
|
|
COutPoint prevout = tx.vin[index].prevout;
|
|
if (prevout.n >= input.non_witness_utxo->vout.size()) {
|
|
return false;
|
|
}
|
|
if (input.non_witness_utxo->GetHash() != prevout.hash) {
|
|
return false;
|
|
}
|
|
utxo = input.non_witness_utxo->vout[prevout.n];
|
|
} else if (!input.witness_utxo.IsNull()) {
|
|
utxo = input.witness_utxo;
|
|
// When we're taking our information from a witness UTXO, we can't verify it is actually data from
|
|
// the output being spent. This is safe in case a witness signature is produced (which includes this
|
|
// information directly in the hash), but not for non-witness signatures. Remember that we require
|
|
// a witness signature in this situation.
|
|
require_witness_sig = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
sigdata.witness = false;
|
|
bool sig_complete;
|
|
if (txdata == nullptr) {
|
|
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
|
|
} else {
|
|
MutableTransactionSignatureCreator creator(tx, index, utxo.nValue, txdata, sighash);
|
|
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
|
|
}
|
|
// Verify that a witness signature was produced in case one was required.
|
|
if (require_witness_sig && !sigdata.witness) return false;
|
|
|
|
// If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
|
|
if (!finalize && sigdata.complete) sigdata.complete = false;
|
|
|
|
input.FromSignatureData(sigdata);
|
|
|
|
// If we have a witness signature, put a witness UTXO.
|
|
if (sigdata.witness) {
|
|
input.witness_utxo = utxo;
|
|
// We can remove the non_witness_utxo if and only if there are no non-segwit or segwit v0
|
|
// inputs in this transaction. Since this requires inspecting the entire transaction, this
|
|
// is something for the caller to deal with (i.e. FillPSBT).
|
|
}
|
|
|
|
// Fill in the missing info
|
|
if (out_sigdata) {
|
|
out_sigdata->missing_pubkeys = sigdata.missing_pubkeys;
|
|
out_sigdata->missing_sigs = sigdata.missing_sigs;
|
|
out_sigdata->missing_redeem_script = sigdata.missing_redeem_script;
|
|
out_sigdata->missing_witness_script = sigdata.missing_witness_script;
|
|
}
|
|
|
|
return sig_complete;
|
|
}
|
|
|
|
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
|
{
|
|
// Finalize input signatures -- in case we have partial signatures that add up to a complete
|
|
// signature, but have not combined them yet (e.g. because the combiner that created this
|
|
// PartiallySignedTransaction did not understand them), this will combine them into a final
|
|
// script.
|
|
bool complete = true;
|
|
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
|
|
}
|
|
|
|
return complete;
|
|
}
|
|
|
|
bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result)
|
|
{
|
|
// It's not safe to extract a PSBT that isn't finalized, and there's no easy way to check
|
|
// whether a PSBT is finalized without finalizing it, so we just do this.
|
|
if (!FinalizePSBT(psbtx)) {
|
|
return false;
|
|
}
|
|
|
|
result = *psbtx.tx;
|
|
for (unsigned int i = 0; i < result.vin.size(); ++i) {
|
|
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
|
|
result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs)
|
|
{
|
|
out = psbtxs[0]; // Copy the first one
|
|
|
|
// Merge
|
|
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
|
|
if (!out.Merge(*it)) {
|
|
return TransactionError::PSBT_MISMATCH;
|
|
}
|
|
}
|
|
return TransactionError::OK;
|
|
}
|
|
|
|
std::string PSBTRoleName(PSBTRole role) {
|
|
switch (role) {
|
|
case PSBTRole::CREATOR: return "creator";
|
|
case PSBTRole::UPDATER: return "updater";
|
|
case PSBTRole::SIGNER: return "signer";
|
|
case PSBTRole::FINALIZER: return "finalizer";
|
|
case PSBTRole::EXTRACTOR: return "extractor";
|
|
// no default case, so the compiler can warn about missing cases
|
|
}
|
|
assert(false);
|
|
}
|
|
|
|
bool DecodeBase64PSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
|
|
{
|
|
auto tx_data = DecodeBase64(base64_tx);
|
|
if (!tx_data) {
|
|
error = "invalid base64";
|
|
return false;
|
|
}
|
|
return DecodeRawPSBT(psbt, MakeByteSpan(*tx_data), error);
|
|
}
|
|
|
|
bool DecodeRawPSBT(PartiallySignedTransaction& psbt, Span<const std::byte> tx_data, std::string& error)
|
|
{
|
|
CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION);
|
|
try {
|
|
ss_data >> psbt;
|
|
if (!ss_data.empty()) {
|
|
error = "extra data after PSBT";
|
|
return false;
|
|
}
|
|
} catch (const std::exception& e) {
|
|
error = e.what();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint32_t PartiallySignedTransaction::GetVersion() const
|
|
{
|
|
if (m_version != std::nullopt) {
|
|
return *m_version;
|
|
}
|
|
return 0;
|
|
}
|