Skip to content

Commit

Permalink
Implement CWallet::SignTransaction using ScriptPubKeyMan::SignTransac…
Browse files Browse the repository at this point in the history
…tion

Summary:
Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [3/9] : bitcoin/bitcoin@f37de92

Depends on D8095

Test Plan:
  ninja all check-all

Reviewers: #bitcoin_abc, PiRK

Reviewed By: PiRK

Differential Revision: https://reviews.bitcoinabc.org/D8096
  • Loading branch information
achow101 authored and deadalnix committed Oct 24, 2020
1 parent 588d250 commit dd2c6ce
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 23 deletions.
96 changes: 74 additions & 22 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2707,39 +2707,79 @@ bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins,
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<COutPoint, Coin> 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<int, std::string> input_errors;
return SignTransaction(tx, coins, SigHashType().withForkId(), input_errors);
}

bool CWallet::SignTransaction(CMutableTransaction &tx,
const std::map<COutPoint, Coin> &coins,
SigHashType sighash,
std::map<int, std::string> &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<uint256> 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<SigningProvider> 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<ScriptPubKeyMan *> 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,
Expand Down Expand Up @@ -4676,6 +4716,18 @@ ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const OutputType &type,
return it->second;
}

std::set<ScriptPubKeyMan *>
CWallet::GetScriptPubKeyMans(const CScript &script,
SignatureData &sigdata) const {
std::set<ScriptPubKeyMan *> 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) {
Expand Down
13 changes: 12 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1097,8 +1097,14 @@ class CWallet final : public WalletStorage,
bool lockUnspents,
const std::set<int> &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<COutPoint, Coin> &coins,
SigHashType sighash,
std::map<int, std::string> &input_errors) const;

/**
* Create a new transaction paying the recipients with a set of coins
Expand Down Expand Up @@ -1427,6 +1433,11 @@ class CWallet final : public WalletStorage,
//! 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<ScriptPubKeyMan *>
GetScriptPubKeyMans(const CScript &script, SignatureData &sigdata) const;

//! Get the SigningProvider for a script
std::unique_ptr<SigningProvider>
GetSigningProvider(const CScript &script) const;
Expand Down

0 comments on commit dd2c6ce

Please sign in to comment.