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

d2774c09cf Clear any input_errors for an input after it is signed (Andrew Chow)
dc174881ad Replace GetSigningProvider with GetSolvingProvider (Andrew Chow)
6a9c429084 Move direct calls to MessageSign into new SignMessage functions in CWallet and ScriptPubKeyMan (Andrew Chow)
82a30fade7 Move key and script filling and signing from CWallet::FillPSBT to ScriptPubKeyMan::FillPSBT (Andrew Chow)
3d70dd99f9 Move FillPSBT to be a member of CWallet (Andrew Chow)
a4af324d15 Use CWallet::SignTransaction in CreateTransaction and signrawtransactionwithwallet (Andrew Chow)
f37de92744 Implement CWallet::SignTransaction using ScriptPubKeyMan::SignTransaction (Andrew Chow)
d999dd588c Add SignTransaction function to ScriptPubKeyMan and LegacyScriptPubKeyMan (Andrew Chow)
2c52b59d0a 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 d2774c09cf
  meshcollider:
    re-utACK d2774c09cf

Tree-SHA512: 89c83e7e7e9315e283fae145a2264648a9d7f7ace8f3281cb3f44f0b013c988d67ba4fa9726e50c643c0ed921bdd269adaec984840d11acf4a681f3e8a582cc1
This commit is contained in:
Samuel Dobson 2020-03-10 08:56:38 +13:00
commit dcf2ccbfde
No known key found for this signature in database
GPG key ID: D300116E1C875A3D
19 changed files with 433 additions and 276 deletions

View file

@ -241,7 +241,6 @@ BITCOIN_CORE_H = \
wallet/fees.h \ wallet/fees.h \
wallet/ismine.h \ wallet/ismine.h \
wallet/load.h \ wallet/load.h \
wallet/psbtwallet.h \
wallet/rpcwallet.h \ wallet/rpcwallet.h \
wallet/scriptpubkeyman.h \ wallet/scriptpubkeyman.h \
wallet/wallet.h \ wallet/wallet.h \
@ -349,7 +348,6 @@ libbitcoin_wallet_a_SOURCES = \
wallet/feebumper.cpp \ wallet/feebumper.cpp \
wallet/fees.cpp \ wallet/fees.cpp \
wallet/load.cpp \ wallet/load.cpp \
wallet/psbtwallet.cpp \
wallet/rpcdump.cpp \ wallet/rpcdump.cpp \
wallet/rpcwallet.cpp \ wallet/rpcwallet.cpp \
wallet/scriptpubkeyman.cpp \ wallet/scriptpubkeyman.cpp \

View file

@ -19,7 +19,6 @@
#include <wallet/fees.h> #include <wallet/fees.h>
#include <wallet/ismine.h> #include <wallet/ismine.h>
#include <wallet/load.h> #include <wallet/load.h>
#include <wallet/psbtwallet.h>
#include <wallet/rpcwallet.h> #include <wallet/rpcwallet.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
@ -119,19 +118,15 @@ public:
} }
bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override 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) { if (provider) {
return provider->GetPubKey(address, pub_key); return provider->GetPubKey(address, pub_key);
} }
return false; 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); return m_wallet->SignMessage(message, pkhash, str_sig);
if (provider) {
return provider->GetKey(address, key);
}
return false;
} }
bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } bool isSpendable(const CTxDestination& dest) override { return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; }
bool haveWatchOnly() override bool haveWatchOnly() override
@ -361,9 +356,9 @@ public:
bool& complete, bool& complete,
int sighash_type = 1 /* SIGHASH_ALL */, int sighash_type = 1 /* SIGHASH_ALL */,
bool sign = true, 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 WalletBalances getBalances() override
{ {

View file

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

View file

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

View file

@ -133,20 +133,27 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
return; return;
} }
CKey key;
if (!model->wallet().getPrivKey(GetScriptForDestination(destination), CKeyID(*pkhash), key))
{
ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
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(); const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
std::string signature; 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("QLabel { color: red; }"); ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }");
ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signing failed.") + QString("</nobr>")); ui->statusLabel_SM->setText(QString("<nobr>") + error + QString("</nobr>"));
return; return;
} }

View file

@ -272,55 +272,27 @@ void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
{ {
int nHashType = ParseSighashString(hashType); int nHashType = ParseSighashString(hashType);
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
// Script verification errors // Script verification errors
std::map<int, std::string> input_errors;
bool complete = SignTransaction(mtx, keystore, coins, nHashType, input_errors);
SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
}
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, std::map<int, std::string>& input_errors, UniValue& result)
{
// Make errors UniValue
UniValue vErrors(UniValue::VARR); UniValue vErrors(UniValue::VARR);
for (const auto& err_pair : input_errors) {
// Use CTransaction for the constant parts of the if (err_pair.second == "Missing amount") {
// transaction to avoid rehashing. // This particular error needs to be an exception for some reason
const CTransaction txConst(mtx); throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coins.at(mtx.vin.at(err_pair.first).prevout).out.ToString()));
// Sign what we can:
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()) {
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
continue;
}
const CScript& prevPubKey = coin->second.out.scriptPubKey;
const CAmount& amount = coin->second.out.nValue;
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);
}
UpdateInput(txin, sigdata);
// amount must be specified for valid segwit signature
if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) {
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing amount for %s", coin->second.out.ToString()));
}
ScriptError serror = SCRIPT_ERR_OK;
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
} else if (serror == SCRIPT_ERR_SIG_NULLFAIL) {
// Verification failed (possibly due to insufficient signatures).
TxInErrorToJSON(txin, vErrors, "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)");
} else {
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
}
} }
TxInErrorToJSON(mtx.vin.at(err_pair.first), vErrors, err_pair.second);
} }
bool fComplete = vErrors.empty();
result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); result.pushKV("hex", EncodeHexTx(CTransaction(mtx)));
result.pushKV("complete", fComplete); result.pushKV("complete", complete);
if (!vErrors.empty()) { if (!vErrors.empty()) {
if (result.exists("errors")) { if (result.exists("errors")) {
vErrors.push_backV(result["errors"].getValues()); vErrors.push_backV(result["errors"].getValues());

View file

@ -6,6 +6,7 @@
#define BITCOIN_RPC_RAWTRANSACTION_UTIL_H #define BITCOIN_RPC_RAWTRANSACTION_UTIL_H
#include <map> #include <map>
#include <string>
class FillableSigningProvider; class FillableSigningProvider;
class UniValue; class UniValue;
@ -24,6 +25,7 @@ class SigningProvider;
* @param result JSON object where signed transaction results accumulate * @param result JSON object where signed transaction results accumulate
*/ */
void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result); void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, const UniValue& hashType, UniValue& result);
void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const std::map<COutPoint, Coin>& coins, std::map<int, std::string>& input_errors, UniValue& result);
/** /**
* Parse a prevtxs UniValue array and get the map of coins from it * Parse a prevtxs UniValue array and get the map of coins from it

View file

@ -465,3 +465,54 @@ bool IsSegWitOutput(const SigningProvider& provider, const CScript& script)
} }
return false; return false;
} }
bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map<COutPoint, Coin>& coins, int nHashType, std::map<int, std::string>& input_errors)
{
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
// Use CTransaction for the constant parts of the
// transaction to avoid rehashing.
const CTransaction txConst(mtx);
// Sign what we can:
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()) {
input_errors[i] = "Input not found or already spent";
continue;
}
const CScript& prevPubKey = coin->second.out.scriptPubKey;
const CAmount& amount = coin->second.out.nValue;
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);
}
UpdateInput(txin, sigdata);
// amount must be specified for valid segwit signature
if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) {
input_errors[i] = "Missing amount";
continue;
}
ScriptError serror = SCRIPT_ERR_OK;
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";
} else if (serror == SCRIPT_ERR_SIG_NULLFAIL) {
// Verification failed (possibly due to insufficient signatures).
input_errors[i] = "CHECK(MULTI)SIG failing with non-zero signature (possibly need more signatures)";
} 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

@ -6,6 +6,7 @@
#ifndef BITCOIN_SCRIPT_SIGN_H #ifndef BITCOIN_SCRIPT_SIGN_H
#define BITCOIN_SCRIPT_SIGN_H #define BITCOIN_SCRIPT_SIGN_H
#include <coins.h>
#include <hash.h> #include <hash.h>
#include <pubkey.h> #include <pubkey.h>
#include <script/interpreter.h> #include <script/interpreter.h>
@ -168,4 +169,7 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script);
/** Check whether a scriptPubKey is known to be segwit. */ /** Check whether a scriptPubKey is known to be segwit. */
bool IsSegWitOutput(const SigningProvider& provider, const CScript& script); bool IsSegWitOutput(const SigningProvider& provider, const CScript& script);
/** Sign the CMutableTransaction */
bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* provider, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors);
#endif // BITCOIN_SCRIPT_SIGN_H #endif // BITCOIN_SCRIPT_SIGN_H

View file

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

View file

@ -1,77 +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.
#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, 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 && input.witness_utxo.IsNull()) {
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.witness_utxo.IsNull()) {
script = input.witness_utxo.scriptPubKey;
} else 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

@ -27,7 +27,6 @@
#include <util/vector.h> #include <util/vector.h>
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/feebumper.h> #include <wallet/feebumper.h>
#include <wallet/psbtwallet.h>
#include <wallet/rpcwallet.h> #include <wallet/rpcwallet.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <wallet/walletdb.h> #include <wallet/walletdb.h>
@ -566,22 +565,12 @@ static UniValue signmessage(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); 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;
CKeyID keyID(*pkhash);
if (!provider->GetKey(keyID, key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
std::string signature; std::string signature;
SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature);
if (!MessageSign(key, strMessage, signature)) { if (err == SigningResult::SIGNING_FAILED) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err));
} else if (err != SigningResult::OK){
throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err));
} }
return signature; return signature;
@ -2973,7 +2962,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("label", i->second.name); 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 (provider) {
if (scriptPubKey.IsPayToScriptHash()) { if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address));
@ -3013,7 +3002,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
entry.pushKV("spendable", out.fSpendable); entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable); entry.pushKV("solvable", out.fSolvable);
if (out.fSolvable) { if (out.fSolvable) {
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey); std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
if (provider) { if (provider) {
auto descriptor = InferDescriptor(scriptPubKey, *provider); auto descriptor = InferDescriptor(scriptPubKey, *provider);
entry.pushKV("desc", descriptor->ToString()); entry.pushKV("desc", descriptor->ToString());
@ -3329,23 +3318,15 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request)
// Parse the prevtxs array // Parse the prevtxs array
ParsePrevouts(request.params[1], nullptr, coins); ParsePrevouts(request.params[1], nullptr, coins);
std::set<std::shared_ptr<SigningProvider>> providers; int nHashType = ParseSighashString(request.params[2]);
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>());
}
// Script verification errors
std::map<int, std::string> input_errors;
bool complete = pwallet->SignTransaction(mtx, coins, nHashType, input_errors);
UniValue result(UniValue::VOBJ); UniValue result(UniValue::VOBJ);
for (std::shared_ptr<SigningProvider> provider : providers) { SignTransactionResultToJSON(mtx, complete, coins, input_errors, result);
SignTransaction(mtx, provider.get(), coins, request.params[2], result); return result;
}
return result;
} }
static UniValue bumpfee(const JSONRPCRequest& request) static UniValue bumpfee(const JSONRPCRequest& request)
@ -3524,7 +3505,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
} else { } else {
PartiallySignedTransaction psbtx(mtx); PartiallySignedTransaction psbtx(mtx);
bool complete = false; bool complete = false;
const TransactionError err = FillPSBT(pwallet, psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */);
CHECK_NONFATAL(err == TransactionError::OK); CHECK_NONFATAL(err == TransactionError::OK);
CHECK_NONFATAL(!complete); CHECK_NONFATAL(!complete);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
@ -3735,7 +3716,7 @@ static UniValue DescribeWalletAddress(const CWallet* const pwallet, const CTxDes
CScript script = GetScriptForDestination(dest); CScript script = GetScriptForDestination(dest);
std::unique_ptr<SigningProvider> provider = nullptr; std::unique_ptr<SigningProvider> provider = nullptr;
if (pwallet) { if (pwallet) {
provider = pwallet->GetSigningProvider(script); provider = pwallet->GetSolvingProvider(script);
} }
ret.pushKVs(detail); ret.pushKVs(detail);
ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider.get()), dest)); ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider.get()), dest));
@ -3837,7 +3818,7 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
CScript scriptPubKey = GetScriptForDestination(dest); CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()));
std::unique_ptr<SigningProvider> provider = pwallet->GetSigningProvider(scriptPubKey); std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
isminetype mine = pwallet->IsMine(dest); isminetype mine = pwallet->IsMine(dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
@ -4141,7 +4122,7 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); 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 bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
bool complete = true; 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) { if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err); throw JSONRPCTransactionError(err);
} }
@ -4264,7 +4245,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
// Fill transaction with out data but don't sign // Fill transaction with out data but don't sign
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool(); bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
bool complete = true; 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) { if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err); throw JSONRPCTransactionError(err);
} }

View file

@ -5,6 +5,7 @@
#include <key_io.h> #include <key_io.h>
#include <outputtype.h> #include <outputtype.h>
#include <script/descriptor.h> #include <script/descriptor.h>
#include <script/sign.h>
#include <util/bip32.h> #include <util/bip32.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/translation.h> #include <util/translation.h>
@ -477,7 +478,7 @@ int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const
return nTimeFirstKey; return nTimeFirstKey;
} }
std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSigningProvider(const CScript& script) const std::unique_ptr<SigningProvider> LegacyScriptPubKeyMan::GetSolvingProvider(const CScript& script) const
{ {
return MakeUnique<LegacySigningProvider>(*this); return MakeUnique<LegacySigningProvider>(*this);
} }
@ -505,6 +506,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, witness or non-witness.
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 if (input.witness_utxo.IsNull()) {
// 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 const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const
{ {
LOCK(cs_KeyStore); LOCK(cs_KeyStore);

View file

@ -5,8 +5,11 @@
#ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H #ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H #define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#include <psbt.h>
#include <script/signingprovider.h> #include <script/signingprovider.h>
#include <script/standard.h> #include <script/standard.h>
#include <util/error.h>
#include <util/message.h>
#include <wallet/crypter.h> #include <wallet/crypter.h>
#include <wallet/ismine.h> #include <wallet/ismine.h>
#include <wallet/walletdb.h> #include <wallet/walletdb.h>
@ -203,13 +206,20 @@ public:
virtual const CKeyMetadata* GetMetadata(const CTxDestination& dest) const { return nullptr; } 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 /** Whether this ScriptPubKeyMan can provide a SigningProvider (via GetSolvingProvider) that, combined with
* sigdata, can produce a valid signature. * sigdata, can produce solving data.
*/ */
virtual bool CanProvide(const CScript& script, SignatureData& sigdata) { return false; } 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(); } virtual uint256 GetID() const { return uint256(); }
/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */ /** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
@ -346,10 +356,14 @@ public:
bool CanGetAddresses(bool internal = false) const override; 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 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; uint256 GetID() const override;
// Map from Key ID to key metadata. // Map from Key ID to key metadata.
@ -447,7 +461,7 @@ public:
std::set<CKeyID> GetKeys() const override; 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 class LegacySigningProvider : public SigningProvider
{ {
private: private:
@ -458,8 +472,8 @@ public:
bool GetCScript(const CScriptID &scriptid, CScript& script) const override { return m_spk_man.GetCScript(scriptid, script); } 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 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 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 GetKey(const CKeyID &address, CKey& key) const override { return false; }
bool HaveKey(const CKeyID &address) const override { return m_spk_man.HaveKey(address); } 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); } 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 <key_io.h>
#include <util/bip32.h> #include <util/bip32.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <wallet/psbtwallet.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
@ -61,7 +60,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Fill transaction with our data // Fill transaction with our data
bool complete = true; 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 // Get the final tx
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
@ -74,9 +73,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
// Try to sign the mutated input // Try to sign the mutated input
SignatureData sigdata; SignatureData sigdata;
psbtx.inputs[0].FillSignatureData(sigdata); BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK);
const std::unique_ptr<SigningProvider> provider = m_wallet.GetSigningProvider(ws1, sigdata);
BOOST_CHECK(!SignPSBTInput(*provider, psbtx, 0, SIGHASH_ALL));
} }
BOOST_AUTO_TEST_CASE(parse_hd_keypath) BOOST_AUTO_TEST_CASE(parse_hd_keypath)

View file

@ -1407,7 +1407,7 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig
const CScript& scriptPubKey = txout.scriptPubKey; const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata; SignatureData sigdata;
std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey); std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey);
if (!provider) { if (!provider) {
// We don't know about this scriptpbuKey; // We don't know about this scriptpbuKey;
return false; return false;
@ -2171,7 +2171,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<
continue; continue;
} }
std::unique_ptr<SigningProvider> provider = GetSigningProvider(wtx.tx->vout[i].scriptPubKey); std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false;
bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable));
@ -2410,34 +2410,172 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return res; return res;
} }
bool CWallet::SignTransaction(CMutableTransaction& tx) bool CWallet::SignTransaction(CMutableTransaction& tx) const
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(cs_wallet);
// sign the new tx // Build coins map
int nIn = 0; std::map<COutPoint, Coin> coins;
for (auto& input : tx.vin) { for (auto& input : tx.vin) {
std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash); std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) { if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
return false; return false;
} }
const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; const CWalletTx& wtx = mi->second;
const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase());
SignatureData sigdata;
std::unique_ptr<SigningProvider> provider = GetSigningProvider(scriptPubKey);
if (!provider) {
// We don't know about this scriptpbuKey;
return false;
}
if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&tx, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
return false;
}
UpdateInput(input, sigdata);
nIn++;
} }
return true; 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 && input.witness_utxo.IsNull()) {
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 ScriptPubKeyMan to use
CScript script;
if (!input.witness_utxo.IsNull()) {
script = input.witness_utxo.scriptPubKey;
} else 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
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, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
@ -2886,25 +3024,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
} }
if (sign) if (sign && !SignTransaction(txNew)) {
{ strFailReason = _("Signing transaction failed").translated;
int nIn = 0; return false;
for (const auto& coin : selected_coins)
{
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))
{
strFailReason = _("Signing transaction failed").translated;
return false;
} else {
UpdateInput(txNew.vin.at(nIn), sigdata);
}
nIn++;
}
} }
// Return the constructed transaction data. // Return the constructed transaction data.
@ -4155,6 +4277,17 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const OutputType& type, bool intern
return it->second; 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 ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const
{ {
SignatureData sigdata; SignatureData sigdata;
@ -4174,17 +4307,17 @@ ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const uint256& id) const
return nullptr; return nullptr;
} }
std::unique_ptr<SigningProvider> CWallet::GetSigningProvider(const CScript& script) const std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript& script) const
{ {
SignatureData sigdata; 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) { for (const auto& spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) { if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second->GetSigningProvider(script); return spk_man_pair.second->GetSolvingProvider(script);
} }
} }
return nullptr; return nullptr;

View file

@ -11,8 +11,10 @@
#include <interfaces/handler.h> #include <interfaces/handler.h>
#include <outputtype.h> #include <outputtype.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <psbt.h>
#include <tinyformat.h> #include <tinyformat.h>
#include <ui_interface.h> #include <ui_interface.h>
#include <util/message.h>
#include <util/strencodings.h> #include <util/strencodings.h>
#include <util/system.h> #include <util/system.h>
#include <validationinterface.h> #include <validationinterface.h>
@ -916,7 +918,30 @@ public:
* calling CreateTransaction(); * calling CreateTransaction();
*/ */
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, 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
*/
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 * Create a new transaction paying the recipients with a set of coins
@ -1153,9 +1178,12 @@ public:
//! Get the ScriptPubKeyMan by id //! Get the ScriptPubKeyMan by id
ScriptPubKeyMan* GetScriptPubKeyMan(const uint256& id) const; 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 //! Get the SigningProvider for a script
std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script) const; std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script) const;
std::unique_ptr<SigningProvider> GetSigningProvider(const CScript& script, SignatureData& sigdata) const; std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript& script, SignatureData& sigdata) const;
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external. //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and external.
LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const;