diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1097,8 +1097,14 @@ bool lockUnspents, const std::set &setSubtractFeeFromOutputs, CCoinControl coinControl); - bool SignTransaction(CMutableTransaction &tx) + // Fetch the inputs and sign with SIGHASH_ALL. + bool SignTransaction(CMutableTransaction &tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // Sign the tx given the input coins and sighash. + bool SignTransaction(CMutableTransaction &tx, + const std::map &coins, + SigHashType sighash, + std::map &input_errors) const; /** * Create a new transaction paying the recipients with a set of coins @@ -1427,6 +1433,11 @@ //! Get the ScriptPubKeyMan by id ScriptPubKeyMan *GetScriptPubKeyMan(const uint256 &id) const; + //! Get all of the ScriptPubKeyMans for a script given additional + //! information in sigdata (populated by e.g. a psbt) + std::set + GetScriptPubKeyMans(const CScript &script, SignatureData &sigdata) const; + //! Get the SigningProvider for a script std::unique_ptr GetSigningProvider(const CScript &script) const; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2707,39 +2707,79 @@ return res; } -bool CWallet::SignTransaction(CMutableTransaction &tx) { +bool CWallet::SignTransaction(CMutableTransaction &tx) const { AssertLockHeld(cs_wallet); - // sign the new tx - int nIn = 0; - for (CTxIn &input : tx.vin) { + + // Build coins map + std::map coins; + for (auto &input : tx.vin) { auto mi = mapWallet.find(input.prevout.GetTxId()); if (mi == mapWallet.end() || input.prevout.GetN() >= mi->second.tx->vout.size()) { return false; } - const CScript &scriptPubKey = - mi->second.tx->vout[input.prevout.GetN()].scriptPubKey; - const Amount amount = mi->second.tx->vout[input.prevout.GetN()].nValue; - SignatureData sigdata; - SigHashType sigHashType = SigHashType().withForkId(); + const CWalletTx &wtx = mi->second; + coins[input.prevout] = + Coin(wtx.tx->vout[input.prevout.GetN()], wtx.m_confirm.block_height, + wtx.IsCoinBase()); + } + std::map input_errors; + return SignTransaction(tx, coins, SigHashType().withForkId(), input_errors); +} + +bool CWallet::SignTransaction(CMutableTransaction &tx, + const std::map &coins, + SigHashType sighash, + std::map &input_errors) const { + // Sign the tx with ScriptPubKeyMans + // Because each ScriptPubKeyMan can sign more than one input, we need to + // keep track of each ScriptPubKeyMan that has signed this transaction. Each + // iteration, we may sign more txins than the txin that is specified in that + // iteration. We assume that each input is signed by only one + // ScriptPubKeyMan. + std::set visited_spk_mans; + for (size_t i = 0; i < tx.vin.size(); i++) { + // Get the prevout + CTxIn &txin = tx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + input_errors[i] = "Input not found or already spent"; + continue; + } - std::unique_ptr provider = - GetSigningProvider(scriptPubKey); - if (!provider) { - // We don't know about this scriptpbuKey; - return false; + // Check if this input is complete + SignatureData sigdata = + DataFromTransaction(tx, i, coin->second.GetTxOut()); + if (sigdata.complete) { + continue; } - if (!ProduceSignature(*provider, - MutableTransactionSignatureCreator( - &tx, nIn, amount, sigHashType), - scriptPubKey, sigdata)) { - return false; + // Input needs to be signed, find the right ScriptPubKeyMan + std::set spk_mans = + GetScriptPubKeyMans(coin->second.GetTxOut().scriptPubKey, sigdata); + if (spk_mans.size() == 0) { + input_errors[i] = "Unable to sign input, missing keys"; + continue; + } + + for (auto &spk_man : spk_mans) { + // If we've already been signed by this spk_man, skip it + if (visited_spk_mans.count(spk_man->GetID()) > 0) { + continue; + } + + // Sign the tx. + // spk_man->SignTransaction will return true if the transaction is + // complete, so we can exit early and return true if that happens. + if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) { + return true; + } + + // Add this spk_man to visited_spk_mans so we can skip it later + visited_spk_mans.insert(spk_man->GetID()); } - UpdateInput(input, sigdata); - nIn++; } - return true; + return false; } bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, @@ -4676,6 +4716,18 @@ return it->second; } +std::set +CWallet::GetScriptPubKeyMans(const CScript &script, + SignatureData &sigdata) const { + std::set spk_mans; + for (const auto &spk_man_pair : m_spk_managers) { + if (spk_man_pair.second->CanProvide(script, sigdata)) { + spk_mans.insert(spk_man_pair.second.get()); + } + } + return spk_mans; +} + ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const CScript &script) const { SignatureData sigdata; for (const auto &spk_man_pair : m_spk_managers) {