Merge #18115: wallet: Pass in transactions and messages for signing instead of exporting the private keys

d2774c09cfcc6c5c967d40bb094eabc8c0bdb6bf Clear any input_errors for an input after it is signed (Andrew Chow)
dc174881ad8498a6905ba282a48077bc5c8037a7 Replace GetSigningProvider with GetSolvingProvider (Andrew Chow)
6a9c429084b40356aa36aa67992da35f61c2f6a2 Move direct calls to MessageSign into new SignMessage functions in CWallet and ScriptPubKeyMan (Andrew Chow)
82a30fade70a2a95c2bbeac4aa06dafda600479d Move key and script filling and signing from CWallet::FillPSBT to ScriptPubKeyMan::FillPSBT (Andrew Chow)
3d70dd99f9f74eef70b19ff6f6f850adc0d5ef8f Move FillPSBT to be a member of CWallet (Andrew Chow)
a4af324d15c1ee43c2abd11a304ae18c7ee82eb0 Use CWallet::SignTransaction in CreateTransaction and signrawtransactionwithwallet (Andrew Chow)
f37de927442d3f024926a66c436d59e391c8696a Implement CWallet::SignTransaction using ScriptPubKeyMan::SignTransaction (Andrew Chow)
d999dd588cab0ff479bc7bee8c9fc33880265ec6 Add SignTransaction function to ScriptPubKeyMan and LegacyScriptPubKeyMan (Andrew Chow)
2c52b59d0a44a86d94fee4e437978d822862c542 Refactor rawtransaction's SignTransaction into generic SignTransaction function (Andrew Chow)

Pull request description:

  Following #17261, the way to sign transactions, PSBTs, and messages was to use `GetSigningProvider()` and get a `SigningProvider` containing the private keys. However this may not be feasible for future `ScriptPubKeyMan`s, such as for hardware wallets. Instead of exporting a `SigningProvider` containing private keys, we need to pass these things into the `ScriptPubKeyMan` (via `CWallet`) so that they can do whatever is needed internally to sign them. This is largely a refactor as the logic of processing transactions, PSBTs, and messages for is moved into `LegacyScriptPubKeyMan` and `CWallet` instead of being handled by the caller (e.g. `signrawtransaction`).

  To help with this, I've refactored the 3(!) implementations of a `SignTransaction()` function into one generic one. This function will be called by `signrawtransactionwithkey` and `LegacyScriptPubKeyMan::SignTransaction()`. `CWallet::CreateTransaction()` is changed to call `CWallet::SignTransaction()` which in turn, calls `LegacyScriptPubKeyMan::SignTransaction()`. Other `ScriptPubKeyMan`s may implement `SignTransaction()` differently.

  `FillPSBT()` is moved to be a member function of `CWallet` and the `psbtwallet.cpp/h` files removed. It is further split so that `CWallet` handles filling the UTXOs while the `ScriptPubKeyMan` handles adding keys, derivation paths, scripts, and signatures. In the end `LegacyScriptPubKeyMan::FillPSBT` still calls `SignPSBTInput`, but the `SigningProvider` is internal to `LegacyScriptPubKeyMan`. Other `ScriptPubKeyMan`s may do something different.

  A new `SignMessage()` function is added to both `CWallet` and `ScriptPubKeyMan`. Instead of having the caller (i.e. `signmessage` or the sign message dialog) get the private key, hash the message, and sign, `ScriptPubKeyMan` will now handle that (`CWallet` passes through to the `ScriptPubKeyMan`s as it does for many functions). This signing code is thus consolidated into `LegacyScriptPubKeyMan::SignMessage()`, though other `ScriptPubKeyMan`s may implement it differently. Additionally, a `SigningError` enum is introduced for the different errors that we expect to see from `SignMessage()`.

  Lastly, `GetSigningProvider()` is renamed to `GetPublicSigningProvider()`. It will now only provide pubkeys, key origins, and scripts. `LegacySigningProvider` has it's `GetKey` and `HaveKey` functions changed to only return false. Future implementations should return `HidingSigningProvider`s where private keys are hidden.

  Other things like `dumpprivkey` and `dumpwallet` are not changed because they directly need and access the `LegacyScriptPubKeyMan` so are not relevant to future changes.

ACKs for top commit:
  instagibbs:
    reACK d2774c09cf
  Sjors:
    re-utACK d2774c09cfcc6c5c967d40bb094eabc8c0bdb6bf
  meshcollider:
    re-utACK d2774c09cfcc6c5c967d40bb094eabc8c0bdb6bf

Tree-SHA512: 89c83e7e7e9315e283fae145a2264648a9d7f7ace8f3281cb3f44f0b013c988d67ba4fa9726e50c643c0ed921bdd269adaec984840d11acf4a681f3e8a582cc1
This commit is contained in:
Samuel Dobson 2020-03-10 08:56:38 +13:00 committed by PastaPastaPasta
parent 39ead664f9
commit f812de4e66
18 changed files with 381 additions and 214 deletions

View File

@ -335,7 +335,6 @@ BITCOIN_CORE_H = \
wallet/fees.h \
wallet/ismine.h \
wallet/load.h \
wallet/psbtwallet.h \
wallet/rpcwallet.h \
wallet/salvage.h \
wallet/scriptpubkeyman.h \
@ -499,7 +498,6 @@ libbitcoin_wallet_a_SOURCES = \
wallet/db.cpp \
wallet/fees.cpp \
wallet/load.cpp \
wallet/psbtwallet.cpp \
wallet/rpcdump.cpp \
wallet/rpcwallet.cpp \
wallet/scriptpubkeyman.cpp \

View File

@ -620,7 +620,7 @@ bool CCoinJoinClientSession::SignFinalTransaction(const CTxMemPool& mempool, con
LogPrint(BCLog::COINJOIN, "CCoinJoinClientSession::%s -- Signing my input %i\n", __func__, nMyInputIndex);
// 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 (!SignSignature(*mixingWallet.GetSigningProvider(prevPubKey), prevPubKey, finalMutableTransaction, nMyInputIndex, 0, int(SIGHASH_ALL | SIGHASH_ANYONECANPAY))) { // changes scriptSig
if (!SignSignature(*mixingWallet.GetSolvingProvider(prevPubKey), prevPubKey, finalMutableTransaction, nMyInputIndex, 0, int(SIGHASH_ALL | SIGHASH_ANYONECANPAY))) { // changes scriptSig
LogPrint(BCLog::COINJOIN, "CCoinJoinClientSession::%s -- Unable to sign my own transaction!\n", __func__);
// not sure what to do here, it will time out...?
}
@ -1541,7 +1541,7 @@ bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& tx
txCollateral.vout.emplace_back(0, CScript() << OP_RETURN);
}
if (!SignSignature(*mixingWallet.GetSigningProvider(txout.scriptPubKey), txout.scriptPubKey, txCollateral, 0, txout.nValue, SIGHASH_ALL)) {
if (!SignSignature(*mixingWallet.GetSolvingProvider(txout.scriptPubKey), txout.scriptPubKey, txCollateral, 0, txout.nValue, SIGHASH_ALL)) {
strReason = "Unable to sign collateral transaction!";
return false;
}

View File

@ -22,7 +22,6 @@
#include <wallet/fees.h>
#include <wallet/ismine.h>
#include <wallet/load.h>
#include <wallet/psbtwallet.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
@ -192,19 +191,15 @@ public:
}
bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override
{
std::unique_ptr<SigningProvider> provider = m_wallet->GetSigningProvider(script);
std::unique_ptr<SigningProvider> provider = m_wallet->GetSolvingProvider(script);
if (provider) {
return provider->GetPubKey(address, pub_key);
}
return false;
}
bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) override
SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) override
{
std::unique_ptr<SigningProvider> provider = m_wallet->GetSigningProvider(script);
if (provider) {
return provider->GetKey(address, key);
}
return false;
return m_wallet->SignMessage(message, pkhash, str_sig);
}
bool isSpendable(const CScript& script) override { return m_wallet->IsMine(script) & ISMINE_SPENDABLE; }
bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; }
@ -404,9 +399,9 @@ public:
bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true,
bool bip32derivs = false) override
bool bip32derivs = false) const override
{
return FillPSBT(m_wallet.get(), psbtx, complete, sighash_type, sign, bip32derivs);
return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs);
}
WalletBalances getBalances() override
{

View File

@ -12,7 +12,7 @@
#include <script/standard.h> // For CTxDestination
#include <support/allocators/secure.h> // For SecureString
#include <ui_interface.h> // For ChangeType
#include <util/system.h>
#include <util/message.h>
#include <functional>
#include <map>
@ -117,8 +117,8 @@ public:
//! Get public key.
virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0;
//! Get private key.
virtual bool getPrivKey(const CScript& script, const CKeyID& address, CKey& key) = 0;
//! Sign message
virtual SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) = 0;
//! Return whether wallet has private key.
virtual bool isSpendable(const CScript& script) = 0;
@ -223,7 +223,7 @@ public:
bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true,
bool bip32derivs = false) = 0;
bool bip32derivs = false) const = 0;
//! Get balances.
virtual WalletBalances getBalances() = 0;

View File

@ -26,7 +26,6 @@
#include <ui_interface.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <wallet/psbtwallet.h>
#include <wallet/wallet.h>
#include <QFontMetrics>

View File

@ -161,20 +161,27 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
return;
}
CKey key;
if (!model->wallet().getPrivKey(GetScriptForDestination(destination), CKeyID(*pkhash), key))
{
ui->statusLabel_SM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_SM->setText(tr("Private key for the entered address is not available."));
return;
}
const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::string signature;
SigningResult res = model->wallet().signMessage(message, *pkhash, signature);
if (!MessageSign(key, message, signature)) {
QString error;
switch (res) {
case SigningResult::OK:
error = tr("No error");
break;
case SigningResult::PRIVATE_KEY_NOT_AVAILABLE:
error = tr("Private key for the entered address is not available.");
break;
case SigningResult::SIGNING_FAILED:
error = tr("Message signing failed.");
break;
// no default case, so the compiler can warn about missing cases
}
if (res != SigningResult::OK) {
ui->statusLabel_SM->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>"));
ui->statusLabel_SM->setText(QString("<nobr>") + error + QString("</nobr>"));
return;
}

View File

@ -228,6 +228,10 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
// Make errors UniValue
UniValue vErrors(UniValue::VARR);
for (const auto& err_pair : input_errors) {
if (err_pair.second == "Missing amount") {
// This particular error needs to be an exception for some reason
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
}
TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
}

View File

@ -425,6 +425,9 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
} else {
input_errors[i] = ScriptErrorString(serror);
}
} else {
// If this input succeeds, make sure there is no error set for it
input_errors.erase(i);
}
}
return input_errors.empty();

View File

@ -76,3 +76,17 @@ uint256 MessageHash(const std::string& message)
return hasher.GetHash();
}
std::string SigningResultString(const SigningResult res)
{
switch (res) {
case SigningResult::OK:
return "No error";
case SigningResult::PRIVATE_KEY_NOT_AVAILABLE:
return "Private key not available";
case SigningResult::SIGNING_FAILED:
return "Sign failed";
// no default case, so the compiler can warn about missing cases
}
assert(false);
}

View File

@ -39,6 +39,12 @@ enum class MessageVerificationResult {
OK
};
enum class SigningResult {
OK, //!< No error
PRIVATE_KEY_NOT_AVAILABLE,
SIGNING_FAILED,
};
/** Verify a signed message.
* @param[in] address Signer's bitcoin address, it must refer to a public key.
* @param[in] signature The signature in base64 format.
@ -65,4 +71,6 @@ bool MessageSign(
*/
uint256 MessageHash(const std::string& message);
std::string SigningResultString(const SigningResult res);
#endif // BITCOIN_UTIL_MESSAGE_H

View File

@ -1,76 +0,0 @@
// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/psbtwallet.h>
TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs)
{
LOCK(pwallet->cs_wallet);
// Get all of the previous transactions
complete = true;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// Verify input looks sane. This will check that we have at most one uxto.
if (!input.IsSane()) {
return TransactionError::INVALID_PSBT;
}
// If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo) {
const uint256& txhash = txin.prevout.hash;
const auto it = pwallet->mapWallet.find(txhash);
if (it != pwallet->mapWallet.end()) {
const CWalletTx& wtx = it->second;
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
// The signing code will switch to the smaller witness_utxo if this is ok.
input.non_witness_utxo = wtx.tx;
}
}
// Get the Sighash type
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
return TransactionError::SIGHASH_MISMATCH;
}
// Get the scriptPubKey to know which SigningProvider to use
CScript script;
if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS;
}
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
} else {
// There's no UTXO so we can just skip this now
complete = false;
continue;
}
SignatureData sigdata;
input.FillSignatureData(sigdata);
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(script, sigdata);
if (!provider) {
complete = false;
continue;
}
complete &= SignPSBTInput(HidingSigningProvider(provider.get(), !sign, !bip32derivs), psbtx, i, sighash_type);
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
const CTxOut& out = psbtx.tx->vout.at(i);
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(out.scriptPubKey);
if (provider) {
UpdatePSBTOutput(HidingSigningProvider(provider.get(), true, !bip32derivs), psbtx, i);
}
}
return TransactionError::OK;
}

View File

@ -1,32 +0,0 @@
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_PSBTWALLET_H
#define BITCOIN_WALLET_PSBTWALLET_H
#include <psbt.h>
#include <wallet/wallet.h>
/**
* Fills out a PSBT with information from the wallet. Fills in UTXOs if we have
* them. Tries to sign if sign=true. Sets `complete` if the PSBT is now complete
* (i.e. has all required signatures or signature-parts, and is ready to
* finalize.) Sets `error` and returns false if something goes wrong.
*
* @param[in] pwallet pointer to a wallet
* @param[in] psbtx PartiallySignedTransaction to fill in
* @param[out] complete indicates whether the PSBT is now complete
* @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
* return error
*/
[[nodiscard]] TransactionError FillPSBT(const CWallet* pwallet,
PartiallySignedTransaction& psbtx,
bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true,
bool bip32derivs = true);
#endif // BITCOIN_WALLET_PSBTWALLET_H

View File

@ -29,7 +29,6 @@
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/psbtwallet.h>
#include <wallet/load.h>
#include <wallet/rpcwallet.h>
#include <wallet/scriptpubkeyman.h>
@ -602,21 +601,12 @@ static UniValue signmessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
CScript script_pub_key = GetScriptForDestination(*pkhash);
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(script_pub_key);
if (!provider) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
CKey key;
if (!provider->GetKey(CKeyID(*pkhash), key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
std::string signature;
if (!MessageSign(key, strMessage, signature)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature);
if (err == SigningResult::SIGNING_FAILED) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err));
} else if (err != SigningResult::OK){
throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
}
return signature;
@ -3160,7 +3150,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("label", i->second.name);
}
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID& hash = CScriptID(std::get<ScriptHash>(address));
@ -3178,7 +3168,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable);
if (out.fSolvable) {
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) {
auto descriptor = InferDescriptor(scriptPubKey, *provider);
entry.pushKV("desc", descriptor->ToString());
@ -3459,23 +3449,15 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
// Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins);
std::set<std::shared_ptr<SigningProvider>> providers;
for (const std::pair<COutPoint, Coin> coin_pair : coins) {
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(coin_pair.second.out.scriptPubKey);
if (provider) {
providers.insert(std::move(provider));
}
}
if (providers.size() == 0) {
// When there are no available providers, use a dummy SigningProvider so we can check if the tx is complete
providers.insert(std::make_shared<SigningProvider>());
}
int nHashType = ParseSighashString(request.params[2]);
// Script verification errors
std::map<int, std::string> input_errors;
bool complete = pwallet->SignTransaction(mtx, coins, nHashType, input_errors);
UniValue result(UniValue::VOBJ);
for (std::shared_ptr<SigningProvider> provider : providers) {
SignTransaction(mtx, provider.get(), coins, request.params[2], result);
}
return result;
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
return result;
}
static UniValue rescanblockchain(const JSONRPCRequest& request)
@ -3621,7 +3603,7 @@ static UniValue DescribeWalletAddress(const CWallet* const pwallet, const CTxDes
CScript script = GetScriptForDestination(dest);
std::unique_ptr<SigningProvider> provider = nullptr;
if (pwallet) {
provider = pwallet->GetSigningProvider(script);
provider = pwallet->GetSolvingProvider(script);
}
ret.pushKVs(detail);
ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
@ -3714,7 +3696,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey);
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
isminetype mine = pwallet->IsMine(dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
@ -3938,7 +3920,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
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;
const TransactionError err = FillPSBT(pwallet, psbtx, complete, nHashType, sign, bip32derivs);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, nHashType, sign, bip32derivs);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}
@ -4049,7 +4031,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
// Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true;
const TransactionError err = FillPSBT(pwallet, psbtx, complete, 1, false, bip32derivs);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, 1, false, bip32derivs);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
}

View File

@ -6,6 +6,7 @@
#include <chainparams.h>
#include <logging.h>
#include <script/descriptor.h>
#include <script/sign.h>
#include <ui_interface.h>
#include <util/bip32.h>
#include <util/strencodings.h>
@ -649,7 +650,7 @@ int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const
return nTimeFirstKey;
}
std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSigningProvider(const CScript& script) const
std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSolvingProvider(const CScript& script) const
{
return std::make_unique<LegacySigningProvider>(*this);
}
@ -677,6 +678,67 @@ bool LegacyScriptPubKeyMan::CanProvide(const CScript& script, SignatureData& sig
}
}
bool LegacyScriptPubKeyMan::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const
{
return ::SignTransaction(tx, this, coins, sighash, input_errors);
}
SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
{
CKeyID key_id(pkhash);
CKey key;
if (!GetKey(key_id, key)) {
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
if (MessageSign(key, message, str_sig)) {
return SigningResult::OK;
}
return SigningResult::SIGNING_FAILED;
}
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs) const
{
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// Verify input looks sane. This will check that we have at most one uxto.
if (!input.IsSane()) {
return TransactionError::INVALID_PSBT;
}
// Get the Sighash type
if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
return TransactionError::SIGHASH_MISMATCH;
}
// Check non_witness_utxo has specified prevout
if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS;
}
} else {
// There's no UTXO so we can just skip this now
continue;
}
SignatureData sigdata;
input.FillSignatureData(sigdata);
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type);
}
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i);
}
return TransactionError::OK;
}
const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
{
LOCK(cs_KeyStore);

View File

@ -5,8 +5,11 @@
#ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#include <psbt.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <util/error.h>
#include <util/message.h>
#include <util/time.h>
#include <wallet/crypter.h>
#include <wallet/ismine.h>
@ -190,13 +193,20 @@ public:
virtual const CKeyMetadata* GetMetadata(const CTxDestination& dest) const { return nullptr; }
virtual std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const { return nullptr; }
virtual std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const { return nullptr; }
/** Whether this ScriptPubKeyMan can provide a SigningProvider (via GetSigningProvider) that, combined with
* sigdata, can produce a valid signature.
/** Whether this ScriptPubKeyMan can provide a SigningProvider (via GetSolvingProvider) that, combined with
* sigdata, can produce solving data.
*/
virtual bool CanProvide(const CScript& script, SignatureData& sigdata) { return false; }
/** Creates new signatures and adds them to the transaction. Returns whether all inputs were signed */
virtual bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const { return false; }
/** Sign a message with the given script */
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
/** 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) const { return TransactionError::INVALID_PSBT; }
virtual uint256 GetID() const { return uint256(); }
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
@ -332,10 +342,14 @@ public:
bool CanGetAddresses(bool internal = false) const override;
std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const override;
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const override;
bool CanProvide(const CScript& script, SignatureData& sigdata) override;
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false) const override;
uint256 GetID() const override;
// Map from Key ID to key metadata.
@ -466,7 +480,7 @@ public:
std::set<CKeyID> GetKeys() const override;
};
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr */
/** Wraps a LegacyScriptPubKeyMan so that it can be returned in a new unique_ptr. Does not provide privkeys */
class LegacySigningProvider : public SigningProvider
{
private:
@ -477,8 +491,8 @@ public:
bool GetCScript(const CScriptID &scriptid, CScript& script) const override { return m_spk_man.GetCScript(scriptid, script); }
bool HaveCScript(const CScriptID &scriptid) const override { return m_spk_man.HaveCScript(scriptid); }
bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const override { return m_spk_man.GetPubKey(address, pubkey); }
bool GetKey(const CKeyID &address, CKey& key) const override { return m_spk_man.GetKey(address, key); }
bool HaveKey(const CKeyID &address) const override { return m_spk_man.HaveKey(address); }
bool GetKey(const CKeyID &address, CKey& key) const override { return false; }
bool HaveKey(const CKeyID &address) const override { return false; }
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
};

View File

@ -5,7 +5,6 @@
#include <key_io.h>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <wallet/psbtwallet.h>
#include <wallet/wallet.h>
#include <boost/test/unit_test.hpp>
@ -60,7 +59,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data
bool complete = true;
BOOST_REQUIRE_EQUAL(TransactionError::OK, FillPSBT(&m_wallet, psbtx, complete, SIGHASH_ALL, false, true));
BOOST_REQUIRE_EQUAL(TransactionError::OK, m_wallet.FillPSBT(psbtx, complete, SIGHASH_ALL, false, true));
// Get the final tx
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
@ -74,9 +73,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input
SignatureData sigdata;
psbtx.inputs[0].FillSignatureData(sigdata);
const std::unique_ptr<SigningProvider> provider = m_wallet.GetSigningProvider(ws1, sigdata);
BOOST_CHECK(!SignPSBTInput(*provider, psbtx, 0, SIGHASH_ALL));
BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK);
}
BOOST_AUTO_TEST_CASE(parse_hd_keypath)

View File

@ -1674,7 +1674,7 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
if (!provider) {
// We don't know about this scriptpbuKey;
return false;
@ -2595,7 +2595,7 @@ void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const
continue;
}
std::unique_ptr<SigningProvider> provider = GetSigningProvider(pcoin->tx->vout[i].scriptPubKey);
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(pcoin->tx->vout[i].scriptPubKey);
bool solvable = provider ? IsSolvable(*provider, pcoin->tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
@ -2862,6 +2862,175 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return res;
}
// KNST KNST TODO
/// !!!!!!!! check with psbtwallet
bool CWallet::SignTransaction(CMutableTransaction& tx) const
{
AssertLockHeld(cs_wallet);
// Build coins map
std::map<COutPoint, Coin> coins;
for (auto& input : tx.vin) {
std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
return false;
}
const CWalletTx& wtx = mi->second;
coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
}
std::map<int, std::string> input_errors;
return SignTransaction(tx, coins, SIGHASH_ALL, input_errors);
}
bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int 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 (unsigned int 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;
}
// Check if this input is complete
SignatureData sigdata = DataFromTransaction(tx, i, coin->second.out);
if (sigdata.complete) {
continue;
}
// Input needs to be signed, find the right ScriptPubKeyMan
std::set<ScriptPubKeyMan*> spk_mans = GetScriptPubKeyMans(coin->second.out.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());
}
}
return false;
}
TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs) const
{
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// Verify input looks sane. This will check that we have at most one uxto, witness or non-witness.
if (!input.IsSane()) {
return TransactionError::INVALID_PSBT;
}
// If we have no utxo, grab it from the wallet.
if (!input.non_witness_utxo) {
const uint256& txhash = txin.prevout.hash;
const auto it = mapWallet.find(txhash);
if (it != mapWallet.end()) {
const CWalletTx& wtx = it->second;
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
// The signing code will switch to the smaller witness_utxo if this is ok.
input.non_witness_utxo = wtx.tx;
}
}
}
// Fill in information from ScriptPubKeyMans
// Because each ScriptPubKeyMan may be able to fill more than one input, we need to keep track of each ScriptPubKeyMan that has filled this psbt.
// Each iteration, we may fill more inputs than the input that is specified in that iteration.
// We assume that each input is filled by only one ScriptPubKeyMan
std::set<uint256> visited_spk_mans;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn& txin = psbtx.tx->vin[i];
PSBTInput& input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// Get the scriptPubKey to know which SigningProvider to use
CScript script;
if (input.non_witness_utxo) {
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
return TransactionError::MISSING_INPUTS;
}
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
} else {
// There's no UTXO so we can just skip this now
complete = false;
continue;
}
SignatureData sigdata;
input.FillSignatureData(sigdata);
std::set<ScriptPubKeyMan*> spk_mans = GetScriptPubKeyMans(script, sigdata);
if (spk_mans.size() == 0) {
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;
}
// Fill in the information from the spk_man
TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
if (res != TransactionError::OK) {
return res;
}
// Add this spk_man to visited_spk_mans so we can skip it later
visited_spk_mans.insert(spk_man->GetID());
}
}
// Complete if every input is now signed
complete = true;
for (const auto& input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}
return TransactionError::OK;
}
SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const
{
SignatureData sigdata;
CScript script_pub_key = GetScriptForDestination(pkhash);
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
}
}
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
{
std::vector<CRecipient> vecSend;
@ -3533,25 +3702,9 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac
// Make sure change position was updated one way or another
assert(nChangePosInOut != std::numeric_limits<int>::max());
if (sign)
{
int nIn = 0;
for(const auto& coin : vecCoins)
{
const CScript& scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
{
error = _("Signing transaction failed");
return false;
} else {
UpdateInput(txNew.vin.at(nIn), sigdata);
}
nIn++;
}
if (sign && !SignTransaction(txNew)) {
error = _("Signing transaction failed");
return false;
}
// Return the constructed transaction data.
@ -5090,6 +5243,17 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(bool internal) const
return spk_manager;
}
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;
@ -5109,17 +5273,17 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const uint256& id) const
return nullptr;
}
std::unique_ptr<SigningProvider> CWallet::GetSigningProvider(const CScript& script) const
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script) const
{
SignatureData sigdata;
return GetSigningProvider(script, sigdata);
return GetSolvingProvider(script, sigdata);
}
std::unique_ptr<SigningProvider> CWallet::GetSigningProvider(const CScript& script, SignatureData& sigdata) const
std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script, SignatureData& sigdata) const
{
for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second->GetSigningProvider(script);
return spk_man_pair.second->GetSolvingProvider(script);
}
}
return nullptr;

View File

@ -11,9 +11,11 @@
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <policy/feerate.h>
#include <psbt.h>
#include <saltedhasher.h>
#include <tinyformat.h>
#include <ui_interface.h>
#include <util/message.h>
#include <util/string.h>
#include <util/system.h>
#include <util/strencodings.h>
@ -1009,7 +1011,30 @@ public:
* calling CreateTransaction();
*/
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
bool SignTransaction(CMutableTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// 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, int sighash, std::map<int, std::string>& input_errors) const;
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const;
/**
* Fills out a PSBT with information from the wallet. Fills in UTXOs if we have
* them. Tries to sign if sign=true. Sets `complete` if the PSBT is now complete
* (i.e. has all required signatures or signature-parts, and is ready to
* finalize.) Sets `error` and returns false if something goes wrong.
*
* @param[in] psbtx PartiallySignedTransaction to fill in
* @param[out] complete indicates whether the PSBT is now complete
* @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
* return error
*/
[[nodiscard]] TransactionError FillPSBT(PartiallySignedTransaction& psbtx,
bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true,
bool bip32derivs = true) const;
/**
* Create a new transaction paying the recipients with a set of coins
@ -1273,9 +1298,12 @@ public:
//! 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;
std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script, SignatureData& sigdata) const;
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const;
std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script, SignatureData& sigdata) const;
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;