From 4a089204660cebed46e3d1164a97707630ea63ba Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 1 Mar 2021 16:49:59 -0800 Subject: [PATCH 01/12] partial bitcoin#21330: Deal with missing data in signature hashes more consistently excludes: - 497718b467330b2c6bb0d44786020c55f1aa75f9 (SegWit) - 725d7ae0494d4a45f5a840bbbd19c008a7363965 (signet) --- src/coinjoin/server.cpp | 2 +- src/script/bitcoinconsensus.cpp | 2 +- src/script/interpreter.cpp | 16 +++++++++++-- src/script/interpreter.h | 14 ++++++++++-- src/script/sigcache.h | 2 +- src/script/sign.cpp | 6 ++--- src/test/evo_deterministicmns_tests.cpp | 2 +- src/test/fuzz/script_flags.cpp | 2 +- src/test/multisig_tests.cpp | 16 ++++++------- src/test/script_p2sh_tests.cpp | 2 +- src/test/script_tests.cpp | 30 ++++++++++++------------- src/test/transaction_tests.cpp | 2 +- 12 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/coinjoin/server.cpp b/src/coinjoin/server.cpp index 6e0ee38474b7f..7ed58cadbb3d2 100644 --- a/src/coinjoin/server.cpp +++ b/src/coinjoin/server.cpp @@ -556,7 +556,7 @@ bool CCoinJoinServer::IsInputScriptSigValid(const CTxIn& txin) const txNew.vin[nTxInIndex].scriptSig = txin.scriptSig; LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0, 24)); // TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit) - if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0))) { + if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0, MissingDataBehavior::ASSERT_FAIL))) { LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex); return false; } diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index f1a11cf5ef4df..f6ac1c37a631a 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -90,7 +90,7 @@ int dashconsensus_verify_script(const unsigned char *scriptPubKey, unsigned int PrecomputedTransactionData txdata(tx); CAmount am(0); - return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), flags, TransactionSignatureChecker(&tx, nIn, am, txdata), nullptr); + return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), flags, TransactionSignatureChecker(&tx, nIn, am, txdata, MissingDataBehavior::FAIL), nullptr); } catch (const std::exception&) { return set_error(err, dashconsensus_ERR_TX_DESERIALIZE); // Error deserializing } diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 35d60d5061f6f..4b6a697e4b5a5 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1516,10 +1516,22 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); -template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector&& spent_outputs); template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector&& spent_outputs); +template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); +template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); + +static bool HandleMissingData(MissingDataBehavior mdb) +{ + switch (mdb) { + case MissingDataBehavior::ASSERT_FAIL: + assert(!"Missing data"); + break; + case MissingDataBehavior::FAIL: + return false; + } + assert(!"Unknown MissingDataBehavior value"); +} template uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) diff --git a/src/script/interpreter.h b/src/script/interpreter.h index baa3ec03ebd44..0c9449c629722 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -156,11 +156,21 @@ class BaseSignatureChecker virtual ~BaseSignatureChecker() {} }; +/** Enum to specify what *TransactionSignatureChecker's behavior should be + * when dealing with missing transaction data. + */ +enum class MissingDataBehavior +{ + ASSERT_FAIL, //!< Abort execution through assertion failure (for consensus code) + FAIL, //!< Just act as if the signature was invalid +}; + template class GenericTransactionSignatureChecker : public BaseSignatureChecker { private: const T* txTo; + const MissingDataBehavior m_mdb; unsigned int nIn; const CAmount amount; const PrecomputedTransactionData* txdata; @@ -169,8 +179,8 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker virtual bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const; public: - GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} - GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {} + GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; diff --git a/src/script/sigcache.h b/src/script/sigcache.h index 3a3b05eea6df0..93774ec9b4c62 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -26,7 +26,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker bool store; public: - CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, PrecomputedTransactionData& txdataIn, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn, amount, txdataIn), store(storeIn) {} + CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amount, PrecomputedTransactionData& txdataIn, bool storeIn=true) : TransactionSignatureChecker(txToIn, nInIn, amount, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {} bool VerifySignature(const std::vector& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override; }; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 63bab32f70271..1c94398bfbedd 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -16,7 +16,7 @@ typedef std::vector valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {} bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { @@ -249,7 +249,7 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI Stacks stack(data); // Get signatures - MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue); + MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue, MissingDataBehavior::FAIL); SignatureExtractorChecker extractor_checker(data, tx_checker); if (VerifyScript(data.scriptSig, txout.scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) { data.complete = true; @@ -414,7 +414,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, UpdateInput(txin, sigdata); ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). input_errors[i] = Untranslated("Unable to sign input, invalid stack size (possibly missing key)"); diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 8783081f9c7d6..acd9956886a36 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -229,7 +229,7 @@ static bool CheckTransactionSignature(const CTxMemPool& mempool, const CMutableT BOOST_REQUIRE(txFrom); CAmount amount = txFrom->vout[txin.prevout.n].nValue; - if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount))) { + if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount, MissingDataBehavior::ASSERT_FAIL))) { return false; } } diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp index c31350d31760f..9fac0af28722d 100644 --- a/src/test/fuzz/script_flags.cpp +++ b/src/test/fuzz/script_flags.cpp @@ -42,7 +42,7 @@ FUZZ_TARGET(script_flags) prevout.nValue = 1; } - const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata}; + const TransactionSignatureChecker checker{&tx, i, prevout.nValue, txdata, MissingDataBehavior::ASSERT_FAIL}; ScriptError serror; const bool ret = VerifyScript(tx.vin.at(i).scriptSig, prevout.scriptPubKey, verify_flags, checker, &serror); diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index ab199554c592f..8d4d243f3ceb2 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -77,20 +77,20 @@ BOOST_AUTO_TEST_CASE(multisig_verify) keys.assign(1,key[0]); keys.push_back(key[1]); s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); - BOOST_CHECK(VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err)); + BOOST_CHECK(VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); for (int i = 0; i < 4; i++) { keys.assign(1,key[i]); s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 1: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a&b 1: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); keys.assign(1,key[1]); keys.push_back(key[i]); s = sign_multisig(a_and_b, keys, CTransaction(txTo[0]), 0); - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount), &err), strprintf("a&b 2: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_and_b, flags, MutableTransactionSignatureChecker(&txTo[0], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a&b 2: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } @@ -101,18 +101,18 @@ BOOST_AUTO_TEST_CASE(multisig_verify) s = sign_multisig(a_or_b, keys, CTransaction(txTo[1]), 0); if (i == 0 || i == 1) { - BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a|b: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } else { - BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err), strprintf("a|b: %d", i)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("a|b: %d", i)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } } s.clear(); s << OP_0 << OP_1; - BOOST_CHECK(!VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount), &err)); + BOOST_CHECK(!VerifyScript(s, a_or_b, flags, MutableTransactionSignatureChecker(&txTo[1], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_SIG_DER, ScriptErrorString(err)); @@ -124,12 +124,12 @@ BOOST_AUTO_TEST_CASE(multisig_verify) s = sign_multisig(escrow, keys, CTransaction(txTo[2]), 0); if (i < j && i < 3 && j < 3) { - BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 1: %d %d", i, j)); + BOOST_CHECK_MESSAGE(VerifyScript(s, escrow, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("escrow 1: %d %d", i, j)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); } else { - BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount), &err), strprintf("escrow 2: %d %d", i, j)); + BOOST_CHECK_MESSAGE(!VerifyScript(s, escrow, flags, MutableTransactionSignatureChecker(&txTo[2], 0, amount, MissingDataBehavior::ASSERT_FAIL), &err), strprintf("escrow 2: %d %d", i, j)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } } diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index b7da5267e944d..962f8f0a57bb0 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -41,7 +41,7 @@ Verify(const CScript& scriptSig, const CScript& scriptPubKey, bool fStrict, Scri txTo.vin[0].scriptSig = scriptSig; txTo.vout[0].nValue = 1; - return VerifyScript(scriptSig, scriptPubKey, fStrict ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, MutableTransactionSignatureChecker(&txTo, 0, txFrom.vout[0].nValue), &err); + return VerifyScript(scriptSig, scriptPubKey, fStrict ? SCRIPT_VERIFY_P2SH : SCRIPT_VERIFY_NONE, MutableTransactionSignatureChecker(&txTo, 0, txFrom.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err); } diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index d86124fb6c37f..8be04b9bd6574 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -126,7 +126,7 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, uint32_t flag const CTransaction txCredit{BuildCreditingTransaction(scriptPubKey)}; CMutableTransaction tx = BuildSpendingTransaction(scriptSig, txCredit); CMutableTransaction tx2 = tx; - BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue), &err) == expect, message); + BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err) == expect, message); BOOST_CHECK_MESSAGE(err == scriptError, FormatScriptError(err) + " where " + FormatScriptError((ScriptError_t)scriptError) + " expected: " + message); // Verify that removing flags from a passing test or adding flags to a failing test does not change the result. @@ -135,7 +135,7 @@ void DoTest(const CScript& scriptPubKey, const CScript& scriptSig, uint32_t flag uint32_t combined_flags{expect ? (flags & ~extra_flags) : (flags | extra_flags)}; // Weed out some invalid flag combinations. if (combined_flags & SCRIPT_VERIFY_CLEANSTACK && ~combined_flags & SCRIPT_VERIFY_P2SH) continue; - BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, combined_flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue), &err) == expect, message + strprintf(" (with flags %x)", combined_flags)); + BOOST_CHECK_MESSAGE(VerifyScript(scriptSig, scriptPubKey, combined_flags, MutableTransactionSignatureChecker(&tx, 0, txCredit.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err) == expect, message + strprintf(" (with flags %x)", combined_flags)); } #if defined(HAVE_CONSENSUS_LIB) @@ -1011,18 +1011,18 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG12) CMutableTransaction txTo12 = BuildSpendingTransaction(CScript(), txFrom12); CScript goodsig1 = sign_multisig(scriptPubKey12, key1, CTransaction(txTo12)); - BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); txTo12.vout[0].nValue = 2; - BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(goodsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); CScript goodsig2 = sign_multisig(scriptPubKey12, key2, CTransaction(txTo12)); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); CScript badsig1 = sign_multisig(scriptPubKey12, key3, CTransaction(txTo12)); - BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey12, gFlags, MutableTransactionSignatureChecker(&txTo12, 0, txFrom12.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); } @@ -1044,54 +1044,54 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23) std::vector keys; keys.push_back(key1); keys.push_back(key2); CScript goodsig1 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(VerifyScript(goodsig1, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key3); CScript goodsig2 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(VerifyScript(goodsig2, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key3); CScript goodsig3 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(VerifyScript(goodsig3, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_OK, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key2); // Can't re-use sig CScript badsig1 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig1, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key2); keys.push_back(key1); // sigs must be in correct order CScript badsig2 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig2, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key3); keys.push_back(key2); // sigs must be in correct order CScript badsig3 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig3, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key4); keys.push_back(key2); // sigs must match pubkeys CScript badsig4 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig4, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); keys.push_back(key1); keys.push_back(key4); // sigs must match pubkeys CScript badsig5 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig5, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EVAL_FALSE, ScriptErrorString(err)); keys.clear(); // Must have signatures CScript badsig6 = sign_multisig(scriptPubKey23, keys, CTransaction(txTo23)); - BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue), &err)); + BOOST_CHECK(!VerifyScript(badsig6, scriptPubKey23, gFlags, MutableTransactionSignatureChecker(&txTo23, 0, txFrom23.vout[0].nValue, MissingDataBehavior::ASSERT_FAIL), &err)); BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_INVALID_STACK_OPERATION, ScriptErrorString(err)); } diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index a83689cfd826a..66712cc360c88 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -96,7 +96,7 @@ bool CheckTxScripts(const CTransaction& tx, const std::map& const CAmount amount = 0; try { tx_valid = VerifyScript(input.scriptSig, map_prevout_scriptPubKeys.at(input.prevout), - flags, TransactionSignatureChecker(&tx, i, amount, txdata), &err); + flags, TransactionSignatureChecker(&tx, i, amount, txdata, MissingDataBehavior::ASSERT_FAIL), &err); } catch (...) { BOOST_ERROR("Bad test: " << strTest); return true; // The test format is bad and an error is thrown. Return true to silence further error. From b7c7c7b35edb4118d435b55ee76704c6e4a17ef9 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 21 Dec 2024 09:32:08 +0000 Subject: [PATCH 02/12] partial bitcoin#21365: Basic Taproot signing support for descriptor wallets includes: - e841fb503d7a662bde01ec2e4794faa989265950 - ce9353164bdb6215a62b2b6dcb2121d331796f60 - 5cb6502ac5730ea453edbec4c46027ac2ada97e0 - fd3f6890f3dfd683f6f13db912caf5c4288adf08 - 49487bc3b6038393c1b9c2dbdc04a78ae1178f1a --- src/node/psbt.cpp | 6 ++-- src/psbt.cpp | 31 +++++++++++++++++---- src/psbt.h | 11 ++++++-- src/rpc/rawtransaction.cpp | 3 +- src/script/interpreter.cpp | 6 ++-- src/script/interpreter.h | 2 +- src/script/sign.cpp | 40 ++++++++++++++++++++++++--- src/script/sign.h | 4 ++- src/wallet/scriptpubkeyman.cpp | 9 +++--- src/wallet/scriptpubkeyman.h | 6 ++-- src/wallet/test/psbt_wallet_tests.cpp | 2 +- src/wallet/wallet.cpp | 4 +-- 12 files changed, 95 insertions(+), 29 deletions(-) diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 101abc8953d12..b25b0e5768518 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -22,6 +22,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.tx->vin.size()); + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -60,7 +62,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); // Things are missing if (!complete) { @@ -119,7 +121,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/psbt.cpp b/src/psbt.cpp index 7d03131661da9..e599d53bcb9d3 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -63,12 +63,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const { - PSBTInput input = inputs[input_index]; + 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 { return false; @@ -202,7 +205,24 @@ bool PSBTInputSigned(const PSBTInput& input) return !input.final_script_sig.empty(); } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) +{ + const CMutableTransaction& tx = *psbt.tx; + bool have_all_spent_outputs = true; + std::vector 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) { PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; @@ -233,10 +253,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& } bool sig_complete; - if (use_dummy) { + if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } input.FromSignatureData(sigdata); @@ -258,8 +278,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) // 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, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); } return complete; diff --git a/src/psbt.h b/src/psbt.h index 1b67a55241908..d9df45730ef9f 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -493,11 +493,18 @@ enum class PSBTRole { std::string PSBTRoleName(PSBTRole role); +/** Compute a PrecomputedTransactionData object from a psbt. */ +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); + /** Checks whether a PSBTInput is already signed. */ bool PSBTInputSigned(const PSBTInput& input); -/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Signs a PSBTInput, verifying that all provided data matches what is being signed. + * + * txdata should be the output of PrecomputePSBTData (which can be shared across + * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. + **/ +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); /** Counts the unsigned inputs of a PSBT. */ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 261296e952ad5..ac7203e5f79ca 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1867,6 +1867,7 @@ static RPCHelpMan utxoupdatepsbt() } // Fill the inputs + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs.at(i); @@ -1877,7 +1878,7 @@ static RPCHelpMan utxoupdatepsbt() // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, /*sighash=*/ SIGHASH_ALL); + SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/SIGHASH_ALL); } // Update script/keypath information using descriptor data. diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 4b6a697e4b5a5..612e495830e87 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1500,7 +1500,7 @@ uint256 GetOutputsSHA256(const T& txTo) } // namespace template -void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent_outputs) +void PrecomputedTransactionData::Init(const T& txTo, std::vector&& spent_outputs, bool force) { assert(!m_ready); @@ -1516,8 +1516,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector&& spent_outputs); -template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector&& spent_outputs); +template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector&& spent_outputs, bool force); +template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector&& spent_outputs, bool force); template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 0c9449c629722..36df1363dcf72 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -121,7 +121,7 @@ struct PrecomputedTransactionData PrecomputedTransactionData() = default; template - void Init(const T& tx, std::vector&& spent_outputs); + void Init(const T& tx, std::vector&& spent_outputs, bool force = false); template explicit PrecomputedTransactionData(const T& tx); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 1c94398bfbedd..9aac318061a07 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -16,7 +16,19 @@ typedef std::vector valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL), + m_txdata(nullptr) +{ +} + +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), + checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) : + MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)), + m_txdata(txdata) +{ +} bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { @@ -24,7 +36,7 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid if (!provider.GetKey(address, key)) return false; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion); + uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); @@ -394,6 +406,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, // Use CTransaction for the constant parts of the // transaction to avoid rehashing. const CTransaction txConst(mtx); + + PrecomputedTransactionData txdata; + std::vector spent_outputs; + spent_outputs.resize(mtx.vin.size()); + bool have_all_spent_outputs = true; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + have_all_spent_outputs = false; + } else { + spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey); + } + } + if (have_all_spent_outputs) { + txdata.Init(txConst, std::move(spent_outputs), true); + } else { + txdata.Init(txConst, {}, true); + } + // Sign what we can: for (unsigned int i = 0; i < mtx.vin.size(); i++) { CTxIn& txin = mtx.vin[i]; @@ -408,13 +440,13 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). input_errors[i] = Untranslated("Unable to sign input, invalid stack size (possibly missing key)"); diff --git a/src/script/sign.h b/src/script/sign.h index e1311a2283456..9b5d61ead38eb 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -40,10 +40,12 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { int nHashType; CAmount amount; const MutableTransactionSignatureChecker checker; + const PrecomputedTransactionData* m_txdata; public: MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); - const BaseSignatureChecker& Checker() const override{ return checker; } + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL); + const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; }; diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 66c553cfc9099..66e05d39dc79f 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -733,7 +733,7 @@ bool LegacyScriptPubKeyMan::SignSpecialTxPayload(const uint256& hash, const CKey return CHashSigner::SignHash(hash, key, vchSig); } -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -762,7 +762,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } SignatureData sigdata; input.FillSignatureData(sigdata); - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -2229,7 +2229,7 @@ bool DescriptorScriptPubKeyMan::SignSpecialTxPayload(const uint256& hash, const return CHashSigner::SignHash(hash, key, vchSig); } -TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -2277,7 +2277,8 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } } - SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); + bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { // If sign is false, we assume that we _could_ sign if we get here. This diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 0c833a207d830..9e6b7f70b2950 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -214,7 +214,7 @@ class ScriptPubKeyMan virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; virtual bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const { return false; } /** Adds script and derivation path information to a PSBT, and optionally signs it. */ - virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } + virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } virtual uint256 GetID() const { return uint256(); } @@ -358,7 +358,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; @@ -582,7 +582,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 9680da50708c1..b61c666484c0f 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) // Try to sign the mutated input SignatureData sigdata; - BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK); + BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK); } BOOST_AUTO_TEST_CASE(parse_hd_keypath) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 869af8d63b53a..ce7523080f74c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3105,6 +3105,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp if (n_signed) { *n_signed = 0; } + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { @@ -3131,8 +3132,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { int n_signed_this_spkm = 0; - - TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm); + TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm); if (res != TransactionError::OK) { return res; } From 3219855d4cb99001129357e988bab35508a349f2 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Tue, 28 Sep 2021 01:24:23 +1300 Subject: [PATCH 03/12] merge bitcoin#23106: Ensure wallet is unlocked before signing PSBT with walletprocesspsbt and GUI --- src/qt/psbtoperationsdialog.cpp | 7 ++++++- src/wallet/rpcwallet.cpp | 5 ++++- test/functional/rpc_psbt.py | 10 ++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/qt/psbtoperationsdialog.cpp b/src/qt/psbtoperationsdialog.cpp index 9811e7ec5031a..c30585a040839 100644 --- a/src/qt/psbtoperationsdialog.cpp +++ b/src/qt/psbtoperationsdialog.cpp @@ -74,6 +74,9 @@ void PSBTOperationsDialog::signTransaction() { bool complete; size_t n_signed; + + WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock()); + TransactionError err = m_wallet_model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, &n_signed, m_transaction_data, complete); if (err != TransactionError::OK) { @@ -84,7 +87,9 @@ void PSBTOperationsDialog::signTransaction() updateTransactionDisplay(); - if (!complete && n_signed < 1) { + if (!complete && !ctx.isValid()) { + showStatus(tr("Cannot sign inputs while wallet is locked."), StatusLevel::WARN); + } else if (!complete && n_signed < 1) { showStatus(tr("Could not sign any more inputs."), StatusLevel::WARN); } else if (!complete) { showStatus(tr("Signed %1 inputs, but more signatures are still required.").arg(n_signed), diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index b4ca6de160730..51a0533592281 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4448,7 +4448,7 @@ RPCHelpMan walletprocesspsbt() HELP_REQUIRING_PASSPHRASE, { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, - {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating"}, + {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating (requires wallet to be unlocked)"}, {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" " \"ALL\"\n" " \"NONE\"\n" @@ -4498,6 +4498,9 @@ RPCHelpMan walletprocesspsbt() bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); bool complete = true; + + if (sign) EnsureWalletIsUnlocked(*pwallet); + const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs)}; if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index a23fb9372ad6e..2ea59a821121e 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -47,6 +47,16 @@ def run_test(self): psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt'] assert_equal(psbtx1, psbtx) + # Node 0 should not be able to sign the transaction with the wallet is locked + self.nodes[0].encryptwallet("password") + assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].walletprocesspsbt, psbtx) + + # Node 0 should be able to process without signing though + unsigned_tx = self.nodes[0].walletprocesspsbt(psbtx, False) + assert_equal(unsigned_tx['complete'], False) + + self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000) + # Sign the transaction and send signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt'] final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] From c7a69ccb6d393c2139d80e39fd487cb8d64570ec Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 21 Dec 2024 09:39:30 +0000 Subject: [PATCH 04/12] merge bitcoin#22067: Test and document a basic M-of-N multisig using descriptor wallets and PSBTs --- doc/descriptors.md | 41 +++++ doc/psbt.md | 3 + test/functional/test_runner.py | 1 + .../wallet_multisig_descriptor_psbt.py | 163 ++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100755 test/functional/wallet_multisig_descriptor_psbt.py diff --git a/doc/descriptors.md b/doc/descriptors.md index c90463657afa6..6a94b867c3f5d 100644 --- a/doc/descriptors.md +++ b/doc/descriptors.md @@ -117,6 +117,47 @@ Key order does not matter for `sortedmulti()`. `sortedmulti()` behaves in the sa as `multi()` does but the keys are reordered in the resulting script such that they are lexicographically ordered as described in BIP67. +#### Basic multisig example + +For a good example of a basic M-of-N multisig between multiple participants using descriptor +wallets and PSBTs, as well as a signing flow, see [this functional test](/test/functional/wallet_multisig_descriptor_psbt.py). + +Disclaimers: It is important to note that this example serves as a quick-start and is kept basic for readability. A downside of the approach +outlined here is that each participant must maintain (and backup) two separate wallets: a signer and the corresponding multisig. +It should also be noted that privacy best-practices are not "by default" here - participants should take care to only use the signer to sign +transactions related to the multisig. Lastly, it is not recommended to use anything other than a Bitcoin Core descriptor wallet to serve as your +signer(s). Other wallets, whether hardware or software, likely impose additional checks and safeguards to prevent users from signing transactions that +could lead to loss of funds, or are deemed security hazards. Conforming to various 3rd-party checks and verifications is not in the scope of this example. + +The basic steps are: + + 1. Every participant generates an xpub. The most straightforward way is to create a new descriptor wallet which we will refer to as + the participant's signer wallet. Avoid reusing this wallet for any purpose other than signing transactions from the + corresponding multisig we are about to create. Hint: extract the wallet's xpubs using `listdescriptors` and pick the one from the + `pkh` descriptor since it's least likely to be accidentally reused (legacy addresses) + 2. Create a watch-only descriptor wallet (blank, private keys disabled). Now the multisig is created by importing the two descriptors: + `wsh(sortedmulti(,XPUB1/0/*,XPUB2/0/*,…,XPUBN/0/*))` and `wsh(sortedmulti(,XPUB1/1/*,XPUB2/1/*,…,XPUBN/1/*))` + (one descriptor w/ `0` for receiving addresses and another w/ `1` for change). Every participant does this + 3. A receiving address is generated for the multisig. As a check to ensure step 2 was done correctly, every participant + should verify they get the same addresses + 4. Funds are sent to the resulting address + 5. A sending transaction from the multisig is created using `walletcreatefundedpsbt` (anyone can initiate this). It is simple to do + this in the GUI by going to the `Send` tab in the multisig wallet and creating an unsigned transaction (PSBT) + 6. At least `M` participants check the PSBT with their multisig using `decodepsbt` to verify the transaction is OK before signing it. + 7. (If OK) the participant signs the PSBT with their signer wallet using `walletprocesspsbt`. It is simple to do this in the GUI by + loading the PSBT from file and signing it + 8. The signed PSBTs are collected with `combinepsbt`, finalized w/ `finalizepsbt`, and then the resulting transaction is broadcasted + to the network. Note that any wallet (eg one of the signers or multisig) is capable of doing this. + 9. Checks that balances are correct after the transaction has been included in a block + +You may prefer a daisy chained signing flow where each participant signs the PSBT one after another until +the PSBT has been signed `M` times and is "complete." For the most part, the steps above remain the same, except (6, 7) +change slightly from signing the original PSBT in parallel to signing it in series. `combinepsbt` is not necessary with +this signing flow and the last (`m`th) signer can just broadcast the PSBT after signing. Note that a parallel signing flow may be +preferable in cases where there are more signers. This signing flow is also included in the test / Python example. +[The test](/test/functional/wallet_multisig_descriptor_psbt.py) is meant to be documentation as much as it is a functional test, so +it is kept as simple and readable as possible. + ### BIP32 derived keys and chains Most modern wallet software and hardware uses keys that are derived using diff --git a/doc/psbt.md b/doc/psbt.md index 3b10489a3b391..c40023c437618 100644 --- a/doc/psbt.md +++ b/doc/psbt.md @@ -92,6 +92,9 @@ hardware implementations will typically implement multiple roles simultaneously. #### Multisig with multiple Bitcoin Core instances +For a quick start see [Basic M-of-N multisig example using descriptor wallets and PSBTs](./descriptors.md#basic-multisig-example). +If you are using legacy wallets feel free to continue with the example provided here. + Alice, Bob, and Carol want to create a 2-of-3 multisig address. They're all using Bitcoin Core. We assume their wallets only contain the multisig funds. In case they also have a personal wallet, this can be accomplished through the diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e1c2b7680afec..a5a387643db18 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -251,6 +251,7 @@ 'feature_assumevalid.py', 'example_test.py', 'wallet_txn_doublespend.py --legacy-wallet', + 'wallet_multisig_descriptor_psbt.py', 'wallet_txn_doublespend.py --descriptors', 'feature_backwards_compatibility.py --legacy-wallet', 'feature_backwards_compatibility.py --descriptors', diff --git a/test/functional/wallet_multisig_descriptor_psbt.py b/test/functional/wallet_multisig_descriptor_psbt.py new file mode 100755 index 0000000000000..a8aa1ff812405 --- /dev/null +++ b/test/functional/wallet_multisig_descriptor_psbt.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# Copyright (c) 2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test a basic M-of-N multisig setup between multiple people using descriptor wallets and PSBTs, as well as a signing flow. + +This is meant to be documentation as much as functional tests, so it is kept as simple and readable as possible. +""" + +from test_framework.address import base58_to_byte +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_approx, + assert_equal, +) + + +class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 3 + self.setup_clean_chain = True + self.wallet_names = [] + self.extra_args = [["-keypool=100"]] * self.num_nodes + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + self.skip_if_no_sqlite() + + @staticmethod + def _get_xpub(wallet): + """Extract the wallet's xpubs using `listdescriptors` and pick the one from the `pkh` descriptor since it's least likely to be accidentally reused (legacy addresses).""" + descriptor = next(filter(lambda d: d["desc"].startswith("pkh"), wallet.listdescriptors()["descriptors"])) + return descriptor["desc"].split("]")[-1].split("/")[0] + + @staticmethod + 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.""" + tx = multisig.decodepsbt(psbt)["tx"] + amount = 0 + for vout in tx["vout"]: + address = vout["scriptPubKey"]["address"] + assert_equal(multisig.getaddressinfo(address)["ischange"], address != to) + if address == to: + amount += vout["value"] + assert_approx(amount, float(value), vspan=0.001) + + def participants_create_multisigs(self, xpubs): + """The multisig is created by importing the following descriptors. The resulting wallet is watch-only and every participant can do this.""" + # some simple validation + assert_equal(len(xpubs), self.N) + # a sanity-check/assertion, this will throw if the base58 checksum of any of the provided xpubs are invalid + for xpub in xpubs: + base58_to_byte(xpub) + + for i, node in enumerate(self.nodes): + node.createwallet(wallet_name=f"{self.name}_{i}", blank=True, descriptors=True, disable_private_keys=True) + multisig = node.get_wallet_rpc(f"{self.name}_{i}") + external = multisig.getdescriptorinfo(f"sh(sortedmulti({self.M},{f'/0/*,'.join(xpubs)}/0/*))") + internal = multisig.getdescriptorinfo(f"sh(sortedmulti({self.M},{f'/1/*,'.join(xpubs)}/1/*))") + result = multisig.importdescriptors([ + { # receiving addresses (internal: False) + "desc": external["descriptor"], + "active": True, + "internal": False, + "timestamp": "now", + }, + { # change addresses (internal: True) + "desc": internal["descriptor"], + "active": True, + "internal": True, + "timestamp": "now", + }, + ]) + assert all(r["success"] for r in result) + yield multisig + + def run_test(self): + self.M = 2 + self.N = self.num_nodes + self.name = f"{self.M}_of_{self.N}_multisig" + self.log.info(f"Testing {self.name}...") + + participants = { + # Every participant generates an xpub. The most straightforward way is to create a new descriptor wallet. + # This wallet will be the participant's `signer` for the resulting multisig. Avoid reusing this wallet for any other purpose (for privacy reasons). + "signers": [node.get_wallet_rpc(node.createwallet(wallet_name=f"participant_{self.nodes.index(node)}", descriptors=True)["name"]) for node in self.nodes], + # After participants generate and exchange their xpubs they will each create their own watch-only multisig. + # Note: these multisigs are all the same, this justs highlights that each participant can independently verify everything on their own node. + "multisigs": [] + } + + self.log.info("Generate and exchange xpubs...") + xpubs = [self._get_xpub(signer) for signer in participants["signers"]] + + self.log.info("Every participant imports the following descriptors to create the watch-only multisig...") + participants["multisigs"] = list(self.participants_create_multisigs(xpubs)) + + self.log.info("Check that every participant's multisig generates the same addresses...") + for _ in range(10): # we check that the first 10 generated addresses are the same for all participant's multisigs + receive_addresses = [multisig.getnewaddress() for multisig in participants["multisigs"]] + all(address == receive_addresses[0] for address in receive_addresses) + change_addresses = [multisig.getrawchangeaddress() for multisig in participants["multisigs"]] + all(address == change_addresses[0] for address in change_addresses) + + self.log.info("Get a mature utxo to send to the multisig...") + coordinator_wallet = participants["signers"][0] + coordinator_wallet.generatetoaddress(101, coordinator_wallet.getnewaddress()) + + deposit_amount = 6.15 + multisig_receiving_address = participants["multisigs"][0].getnewaddress() + self.log.info("Send funds to the resulting multisig receiving address...") + coordinator_wallet.sendtoaddress(multisig_receiving_address, deposit_amount) + self.nodes[0].generate(1) + self.sync_all() + for participant in participants["multisigs"]: + assert_approx(participant.getbalance(), deposit_amount, vspan=0.001) + + self.log.info("Send a transaction from the multisig!") + to = participants["signers"][self.N - 1].getnewaddress() + value = 1 + self.log.info("First, make a sending transaction, created using `walletcreatefundedpsbt` (anyone can initiate this)...") + psbt = participants["multisigs"][0].walletcreatefundedpsbt(inputs=[], outputs={to: value}, options={"feeRate": 0.00010}) + + psbts = [] + self.log.info("Now at least M users check the psbt with decodepsbt and (if OK) signs it with walletprocesspsbt...") + for m in range(self.M): + signers_multisig = participants["multisigs"][m] + self._check_psbt(psbt["psbt"], to, value, signers_multisig) + signing_wallet = participants["signers"][m] + partially_signed_psbt = signing_wallet.walletprocesspsbt(psbt["psbt"]) + psbts.append(partially_signed_psbt["psbt"]) + + self.log.info("Finally, collect the signed PSBTs with combinepsbt, finalizepsbt, then broadcast the resulting transaction...") + combined = coordinator_wallet.combinepsbt(psbts) + finalized = coordinator_wallet.finalizepsbt(combined) + coordinator_wallet.sendrawtransaction(finalized["hex"]) + + self.log.info("Check that balances are correct after the transaction has been included in a block.") + self.nodes[0].generate(1) + self.sync_all() + assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - value, vspan=0.001) + assert_equal(participants["signers"][self.N - 1].getbalance(), value) + + self.log.info("Send another transaction from the multisig, this time with a daisy chained signing flow (one after another in series)!") + psbt = participants["multisigs"][0].walletcreatefundedpsbt(inputs=[], outputs={to: value}, options={"feeRate": 0.00010}) + for m in range(self.M): + signers_multisig = participants["multisigs"][m] + self._check_psbt(psbt["psbt"], to, value, signers_multisig) + signing_wallet = participants["signers"][m] + psbt = signing_wallet.walletprocesspsbt(psbt["psbt"]) + assert_equal(psbt["complete"], m == self.M - 1) + finalized = coordinator_wallet.finalizepsbt(psbt["psbt"]) + coordinator_wallet.sendrawtransaction(finalized["hex"]) + + self.log.info("Check that balances are correct after the transaction has been included in a block.") + self.nodes[0].generate(1) + self.sync_all() + assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - (value * 2), vspan=0.001) + assert_equal(participants["signers"][self.N - 1].getbalance(), value * 2) + + +if __name__ == "__main__": + WalletMultisigDescriptorPSBTTest().main() From ba85b4c623590fd3fde306e695e6e298144df51a Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 18 Oct 2021 23:14:56 +0300 Subject: [PATCH 05/12] merge bitcoin#23303: Fix wallet_multisig_descriptor_psbt.py --- test/functional/wallet_multisig_descriptor_psbt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/functional/wallet_multisig_descriptor_psbt.py b/test/functional/wallet_multisig_descriptor_psbt.py index a8aa1ff812405..bf6c9eb78d6c5 100755 --- a/test/functional/wallet_multisig_descriptor_psbt.py +++ b/test/functional/wallet_multisig_descriptor_psbt.py @@ -104,13 +104,13 @@ def run_test(self): self.log.info("Get a mature utxo to send to the multisig...") coordinator_wallet = participants["signers"][0] - coordinator_wallet.generatetoaddress(101, coordinator_wallet.getnewaddress()) + self.generatetoaddress(self.nodes[0], 101, coordinator_wallet.getnewaddress()) deposit_amount = 6.15 multisig_receiving_address = participants["multisigs"][0].getnewaddress() self.log.info("Send funds to the resulting multisig receiving address...") coordinator_wallet.sendtoaddress(multisig_receiving_address, deposit_amount) - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) self.sync_all() for participant in participants["multisigs"]: assert_approx(participant.getbalance(), deposit_amount, vspan=0.001) @@ -136,7 +136,7 @@ def run_test(self): coordinator_wallet.sendrawtransaction(finalized["hex"]) self.log.info("Check that balances are correct after the transaction has been included in a block.") - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) self.sync_all() assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - value, vspan=0.001) assert_equal(participants["signers"][self.N - 1].getbalance(), value) @@ -153,7 +153,7 @@ def run_test(self): coordinator_wallet.sendrawtransaction(finalized["hex"]) self.log.info("Check that balances are correct after the transaction has been included in a block.") - self.nodes[0].generate(1) + self.generate(self.nodes[0], 1) self.sync_all() assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - (value * 2), vspan=0.001) assert_equal(participants["signers"][self.N - 1].getbalance(), value * 2) From 193765b6309c8ae86901878c40ebead0e623d2b7 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sun, 31 Oct 2021 13:19:03 +0200 Subject: [PATCH 06/12] merge bitcoin#23403: Fix segfault in the psbt_wallet_tests/psbt_updater_test --- src/wallet/test/wallet_test_fixture.cpp | 6 ++++++ src/wallet/test/wallet_test_fixture.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 8d882e80defa7..93f635da79a9c 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -4,6 +4,7 @@ #include +#include WalletTestingSetup::WalletTestingSetup(const std::string& chainName) : TestingSetup(chainName), @@ -14,3 +15,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName) m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} }); m_wallet_loader->registerRpcs(); } + +WalletTestingSetup::~WalletTestingSetup() +{ + if (m_node.scheduler) m_node.scheduler->stop(); +} diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index 44f6c210598ff..752c8940888ac 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -19,6 +19,7 @@ */ struct WalletTestingSetup : public TestingSetup { explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); + ~WalletTestingSetup(); std::unique_ptr m_wallet_loader; CWallet m_wallet; From 0c52bfeafd2139f081c4e5b046724a78b430bbb7 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:01:38 +0000 Subject: [PATCH 07/12] merge bitcoin#22513: Allow walletprocesspsbt to sign without finalizing --- src/psbt.cpp | 8 ++++++-- src/psbt.h | 2 +- src/rpc/client.cpp | 1 + src/wallet/rpcwallet.cpp | 6 ++++-- src/wallet/scriptpubkeyman.cpp | 8 ++++---- src/wallet/scriptpubkeyman.h | 6 +++--- src/wallet/wallet.cpp | 4 ++-- src/wallet/wallet.h | 5 ++++- test/functional/rpc_psbt.py | 4 +++- 9 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/psbt.cpp b/src/psbt.cpp index e599d53bcb9d3..ec913e8d3a119 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -222,7 +222,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& return txdata; } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata) +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; @@ -259,6 +259,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } + + // If we are not finalizing, set sigdata.complete to false to not set the scriptSig + if (!finalize && sigdata.complete) sigdata.complete = false; + input.FromSignatureData(sigdata); // Fill in the missing info @@ -280,7 +284,7 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) bool complete = true; const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true); } return complete; diff --git a/src/psbt.h b/src/psbt.h index d9df45730ef9f..0a4277622d19e 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -504,7 +504,7 @@ bool PSBTInputSigned(const PSBTInput& input); * txdata should be the output of PrecomputePSBTData (which can be shared across * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. **/ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true); /** Counts the unsigned inputs of a PSBT. */ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index e66797cd88bb1..4ad0c584076ea 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -138,6 +138,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "walletcreatefundedpsbt", 4, "bip32derivs" }, { "walletprocesspsbt", 1, "sign" }, { "walletprocesspsbt", 3, "bip32derivs" }, + { "walletprocesspsbt", 4, "finalize" }, { "createpsbt", 0, "inputs" }, { "createpsbt", 1, "outputs" }, { "createpsbt", 2, "locktime" }, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 51a0533592281..5c67b88bbd84d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4457,6 +4457,7 @@ RPCHelpMan walletprocesspsbt() " \"NONE|ANYONECANPAY\"\n" " \"SINGLE|ANYONECANPAY\""}, {"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"}, + {"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -4470,7 +4471,7 @@ RPCHelpMan walletprocesspsbt() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); + RPCTypeCheck(request.params, {UniValue::VSTR}); std::shared_ptr const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; @@ -4497,11 +4498,12 @@ RPCHelpMan walletprocesspsbt() // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool(); + bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool(); bool complete = true; if (sign) EnsureWalletIsUnlocked(*pwallet); - const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs)}; + const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs, nullptr, finalize)}; if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 66e05d39dc79f..7d78728b9baf6 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -733,7 +733,7 @@ bool LegacyScriptPubKeyMan::SignSpecialTxPayload(const uint256& hash, const CKey return CHashSigner::SignHash(hash, key, vchSig); } -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const { if (n_signed) { *n_signed = 0; @@ -762,7 +762,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } SignatureData sigdata; input.FillSignatureData(sigdata); - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); + SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -2229,7 +2229,7 @@ bool DescriptorScriptPubKeyMan::SignSpecialTxPayload(const uint256& hash, const return CHashSigner::SignHash(hash, key, vchSig); } -TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const { if (n_signed) { *n_signed = 0; @@ -2277,7 +2277,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } } - SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); + SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 9e6b7f70b2950..fc26f4f3f4d53 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -214,7 +214,7 @@ class ScriptPubKeyMan virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; virtual bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const { return false; } /** Adds script and derivation path information to a PSBT, and optionally signs it. */ - virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } + virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return TransactionError::INVALID_PSBT; } virtual uint256 GetID() const { return uint256(); } @@ -358,7 +358,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; uint256 GetID() const override; @@ -582,7 +582,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan bool SignTransaction(CMutableTransaction& tx, const std::map& coins, int sighash, std::map& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; bool SignSpecialTxPayload(const uint256& hash, const CKeyID& keyid, std::vector& vchSig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; uint256 GetID() const override; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ce7523080f74c..8458a439912a8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3100,7 +3100,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::mapFillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm); + TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm, finalize); if (res != TransactionError::OK) { return res; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e3164577e5727..99af94418e70b 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1162,6 +1162,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati * @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify) * @param[in] sign whether to sign or not * @param[in] bip32derivs whether to fill in bip32 derivation information if available + * @param[out] n_signed the number of inputs signed by this wallet + * @param[in] finalize whether to create the final scriptSig * return error */ [[nodiscard]] TransactionError FillPSBT(PartiallySignedTransaction& psbtx, @@ -1169,7 +1171,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = true, - size_t* n_signed = nullptr) const; + size_t* n_signed = nullptr, + bool finalize = true) const; /** * Create a new transaction paying the recipients with a set of coins diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 2ea59a821121e..144bf2803c7d0 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -58,7 +58,9 @@ def run_test(self): self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000) # Sign the transaction and send - signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt'] + signed_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=False)['psbt'] + finalized_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=True)['psbt'] + assert signed_tx != finalized_tx final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] self.nodes[0].sendrawtransaction(final_tx) From f1ba3195b3f269cbda21613905e50e738e5f55fd Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Mon, 15 Nov 2021 13:54:01 -0500 Subject: [PATCH 08/12] partial bitcoin#22514: Actually use SIGHASH_DEFAULT for PSBT signing excludes: - d3992669df826899a3de78a77a366dab46028026 (we don't have taproot) - c0405ee27fa23a9868f04c052bdc94d7accc57e2 (see above) --- src/psbt.h | 10 ++++++---- src/rpc/rawtransaction.cpp | 4 ++-- src/wallet/scriptpubkeyman.cpp | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/psbt.h b/src/psbt.h index 0a4277622d19e..bc8a12a4d9dc4 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -50,7 +50,7 @@ struct PSBTInput std::map hd_keypaths; std::map partial_sigs; std::map, std::vector> unknown; - int sighash_type = 0; + std::optional sighash_type; bool IsNull() const; void FillSignatureData(SignatureData& sigdata) const; @@ -74,9 +74,9 @@ struct PSBTInput } // Write the sighash type - if (sighash_type > 0) { + if (sighash_type != std::nullopt) { SerializeToVector(s, PSBT_IN_SIGHASH); - SerializeToVector(s, sighash_type); + SerializeToVector(s, *sighash_type); } // Write the redeem script @@ -166,7 +166,9 @@ struct PSBTInput } else if (key.size() != 1) { throw std::ios_base::failure("Sighash type key is more than one byte type"); } - UnserializeFromVector(s, sighash_type); + int sighash; + UnserializeFromVector(s, sighash); + sighash_type = sighash; break; case PSBT_IN_REDEEMSCRIPT: { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index ac7203e5f79ca..ff9df9e8bb986 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1465,8 +1465,8 @@ static RPCHelpMan decodepsbt() } // Sighash - if (input.sighash_type > 0) { - in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type)); + if (input.sighash_type != std::nullopt) { + in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type)); } // Redeem script diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 7d78728b9baf6..b65a6819ee2bc 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -747,7 +747,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } // Get the Sighash type - if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) { return TransactionError::SIGHASH_MISMATCH; } @@ -2243,7 +2243,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } // Get the Sighash type - if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + if (sign && input.sighash_type != std::nullopt && *input.sighash_type != sighash_type) { return TransactionError::SIGHASH_MISMATCH; } From c076d0286f934155b0274822b98109fb025b4752 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:49:43 +0000 Subject: [PATCH 09/12] merge bitcoin#17034: PSBT version, proprietary, and xpub fields (BIP 174) --- src/psbt.cpp | 15 ++ src/psbt.h | 282 +++++++++++++++++++++++++++-- src/pubkey.cpp | 12 ++ src/pubkey.h | 19 ++ src/rpc/rawtransaction.cpp | 108 +++++++++++ src/script/keyorigin.h | 19 ++ src/script/sign.cpp | 1 + src/script/sign.h | 75 -------- src/script/signingprovider.h | 3 +- src/serialize.h | 13 ++ src/test/fuzz/script_sign.cpp | 5 +- test/functional/data/rpc_psbt.json | 9 +- 12 files changed, 465 insertions(+), 96 deletions(-) diff --git a/src/psbt.cpp b/src/psbt.cpp index ec913e8d3a119..a5436ca334a06 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -37,6 +37,13 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) 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; @@ -356,3 +363,11 @@ bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, } return true; } + +uint32_t PartiallySignedTransaction::GetVersion() const +{ + if (m_version != std::nullopt) { + return *m_version; + } + return 0; +} diff --git a/src/psbt.h b/src/psbt.h index bc8a12a4d9dc4..91bf3b5b8c459 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -10,8 +10,11 @@ #include #include #include +#include