aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@protonmail.com>2021-02-23 17:56:24 +0100
committerWladimir J. van der Laan <laanwj@protonmail.com>2021-02-23 17:56:43 +0100
commita9335e4f129cadd87fda0fe49baf74670ded491a (patch)
treec16d6d229e204758a32362f8c10a88791d2c03c4 /src
parent78effb37f35ff09733e79497bd6b06d355272d79 (diff)
parentf75e0c1edde39a91cc353b0102638e232def9476 (diff)
downloadbitcoin-a9335e4f129cadd87fda0fe49baf74670ded491a.tar.xz
Merge #16546: External signer support - Wallet Box edition
f75e0c1edde39a91cc353b0102638e232def9476 doc: add external-signer.md (Sjors Provoost) d4b0107d68a91ed4d1a5c78c8ca76251329d3f3c rpc: send: support external signer (Sjors Provoost) 245b4457cf9265190a05529a0a97e1cb258cca8a rpc: signerdisplayaddress (Sjors Provoost) 7ebc7c0215979c53b92a436acc8b5b607b8d735a wallet: ExternalSigner: add GetDescriptors method (Sjors Provoost) fc5da520f5c72287f59823b8a6d748dda49c574a wallet: add GetExternalSigner() (Sjors Provoost) 259f52cc33817a00b91ec9c7d078c07b88db7ab4 test: external_signer wallet flag is immutable (Sjors Provoost) 2655197e1c2dea9536c32afe1482ced4a1f481e9 rpc: add external_signer option to createwallet (Sjors Provoost) 2700f09c4130af6167ce71f46960e92ca800e205 rpc: signer: add enumeratesigners to list external signers (Sjors Provoost) 07b7c940a7da138d55a484ef83fee19ebf58a867 rpc: add external signer RPC files (Sjors Provoost) 8ce7767071779a0170364e6426bd393ed71bf281 wallet: add ExternalSignerScriptPubKeyMan (Sjors Provoost) 157ea7c614950d61bfe405310e2aaabcee31f7a3 wallet: add external_signer flag (Sjors Provoost) f3e6ce78fba2b31173fe7b606aa9edb5b615bff3 test: add external signer test (Sjors Provoost) 8cf543f96dcd6fdfac1367b9e2b1d7d51be8bb76 wallet: add -signer argument for external signer command (Sjors Provoost) f7eb7ecc6750ab267a979d9268ce5b5d151c26de test: framework: add skip_if_no_external_signer (Sjors Provoost) 87a97941f667483bbf2ab00929e03a2199cb8a62 configure: add --enable-external-signer (Sjors Provoost) Pull request description: Big picture overview in [this gist](https://gist.github.com/Sjors/29d06728c685e6182828c1ce9b74483d). This PR lets `bitcoind` call an arbitrary command `-signer=<cmd>`, e.g. a hardware wallet driver, where it can fetch public keys, ask to display an address, and sign a transaction (using PSBT under the hood). It's design to work with https://github.com/bitcoin-core/HWI, which supports multiple hardware wallets. Any command with the same arguments and return values will work. It simplifies the manual procedure described [here](https://github.com/bitcoin-core/HWI/blob/master/docs/bitcoin-core-usage.md). Usage is documented in [doc/external-signer.md]( https://github.com/Sjors/bitcoin/blob/2019/08/hww-box2/doc/external-signer.md), which also describes what protocol a different signer binary should conform to. Use `--enable-external-signer` to opt in, requires Boost::Process: ``` Options used to compile and link: with wallet = yes with gui / qt = no external signer = yes ``` It adds the following RPC methods: * `enumeratesigners`: asks <cmd> for a list of signers (e.g. devices) and their master key fingerprint * `signerdisplayaddress <address>`: asks <cmd> to display an address It enhances the following RPC methods: * `createwallet`: takes an additional `external_signer` argument and fetches keys from device * `send`: automatically sends transaction to device and waits Usage TL&DR: * clone HWI repo somewhere and launch `bitcoind -signer=../HWI/hwi.py` * check if you can see your hardware device: `bitcoin-cli enumeratesigners` * create wallet and auto import keys `bitcoin-cli createwallet "hww" true true "" true true true` * display address on device: `bitcoin-cli signerdisplayaddress ...` * to spend, use `send` RPC and approve transaction on device Prerequisites: - [x] #21127 load wallet flags before everything else - [x] #21182 remove mostly pointless BOOST_PROCESS macro Potentially useful followups: - GUI support: bitcoin-core/gui#4 - bumpfee support - (automatically) verify (a subset of) keys on the device after import, through message signing ACKs for top commit: laanwj: re-ACK f75e0c1edde39a91cc353b0102638e232def9476 Tree-SHA512: 7db8afd54762295c1424c3f01d8c587ec256a72f34bd5256e04b21832dabd5dc212be8ab975ae3b67de75259fd569a561491945750492f417111dc7b6641e77f
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am6
-rw-r--r--src/dummywallet.cpp1
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/test/system_tests.cpp10
-rw-r--r--src/util/error.cpp4
-rw-r--r--src/util/error.h2
-rw-r--r--src/util/system.cpp8
-rw-r--r--src/util/system.h4
-rw-r--r--src/wallet/external_signer.cpp119
-rw-r--r--src/wallet/external_signer.h73
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.cpp81
-rw-r--r--src/wallet/external_signer_scriptpubkeyman.h34
-rw-r--r--src/wallet/init.cpp3
-rw-r--r--src/wallet/interfaces.cpp10
-rw-r--r--src/wallet/rpcsigner.cpp111
-rw-r--r--src/wallet/rpcsigner.h25
-rw-r--r--src/wallet/rpcwallet.cpp14
-rw-r--r--src/wallet/scriptpubkeyman.cpp1
-rw-r--r--src/wallet/scriptpubkeyman.h10
-rw-r--r--src/wallet/wallet.cpp137
-rw-r--r--src/wallet/wallet.h12
-rw-r--r--src/wallet/walletutil.h3
22 files changed, 629 insertions, 40 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 67efbbeae4..8a9ef49a34 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -265,10 +265,13 @@ BITCOIN_CORE_H = \
wallet/crypter.h \
wallet/db.h \
wallet/dump.h \
+ wallet/external_signer.h \
+ wallet/external_signer_scriptpubkeyman.h \
wallet/feebumper.h \
wallet/fees.h \
wallet/ismine.h \
wallet/load.h \
+ wallet/rpcsigner.h \
wallet/rpcwallet.h \
wallet/salvage.h \
wallet/scriptpubkeyman.h \
@@ -379,11 +382,14 @@ libbitcoin_wallet_a_SOURCES = \
wallet/crypter.cpp \
wallet/db.cpp \
wallet/dump.cpp \
+ wallet/external_signer_scriptpubkeyman.cpp \
+ wallet/external_signer.cpp \
wallet/feebumper.cpp \
wallet/fees.cpp \
wallet/interfaces.cpp \
wallet/load.cpp \
wallet/rpcdump.cpp \
+ wallet/rpcsigner.cpp \
wallet/rpcwallet.cpp \
wallet/scriptpubkeyman.cpp \
wallet/wallet.cpp \
diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp
index 4543f098a1..bb06c95e7d 100644
--- a/src/dummywallet.cpp
+++ b/src/dummywallet.cpp
@@ -38,6 +38,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
"-paytxfee=<amt>",
"-rescan",
"-salvagewallet",
+ "-signer=<cmd>",
"-spendzeroconfchange",
"-txconfirmtarget=<n>",
"-wallet=<path>",
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index d1eb849b7e..2b593cd10b 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -183,6 +183,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createwallet", 4, "avoid_reuse"},
{ "createwallet", 5, "descriptors"},
{ "createwallet", 6, "load_on_startup"},
+ { "createwallet", 7, "external_signer"},
{ "loadwallet", 1, "load_on_startup"},
{ "unloadwallet", 1, "load_on_startup"},
{ "getnodeaddresses", 0, "count"},
diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp
index ce555f7299..940145b84f 100644
--- a/src/test/system_tests.cpp
+++ b/src/test/system_tests.cpp
@@ -6,22 +6,22 @@
#include <util/system.h>
#include <univalue.h>
-#ifdef HAVE_BOOST_PROCESS
+#ifdef ENABLE_EXTERNAL_SIGNER
#include <boost/process.hpp>
-#endif // HAVE_BOOST_PROCESS
+#endif // ENABLE_EXTERNAL_SIGNER
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(system_tests, BasicTestingSetup)
-// At least one test is required (in case HAVE_BOOST_PROCESS is not defined).
+// At least one test is required (in case ENABLE_EXTERNAL_SIGNER is not defined).
// Workaround for https://github.com/bitcoin/bitcoin/issues/19128
BOOST_AUTO_TEST_CASE(dummy)
{
BOOST_CHECK(true);
}
-#ifdef HAVE_BOOST_PROCESS
+#ifdef ENABLE_EXTERNAL_SIGNER
bool checkMessage(const std::runtime_error& ex)
{
@@ -90,6 +90,6 @@ BOOST_AUTO_TEST_CASE(run_command)
}
#endif
}
-#endif // HAVE_BOOST_PROCESS
+#endif // ENABLE_EXTERNAL_SIGNER
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util/error.cpp b/src/util/error.cpp
index 76fac4d391..48c81693f3 100644
--- a/src/util/error.cpp
+++ b/src/util/error.cpp
@@ -31,6 +31,10 @@ bilingual_str TransactionErrorString(const TransactionError err)
return Untranslated("Specified sighash value does not match value stored in PSBT");
case TransactionError::MAX_FEE_EXCEEDED:
return Untranslated("Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)");
+ case TransactionError::EXTERNAL_SIGNER_NOT_FOUND:
+ return Untranslated("External signer not found");
+ case TransactionError::EXTERNAL_SIGNER_FAILED:
+ return Untranslated("External signer failed to sign");
// no default case, so the compiler can warn about missing cases
}
assert(false);
diff --git a/src/util/error.h b/src/util/error.h
index 6633498d2b..4cc35eb1fd 100644
--- a/src/util/error.h
+++ b/src/util/error.h
@@ -30,6 +30,8 @@ enum class TransactionError {
PSBT_MISMATCH,
SIGHASH_MISMATCH,
MAX_FEE_EXCEEDED,
+ EXTERNAL_SIGNER_NOT_FOUND,
+ EXTERNAL_SIGNER_FAILED,
};
bilingual_str TransactionErrorString(const TransactionError error);
diff --git a/src/util/system.cpp b/src/util/system.cpp
index 9a2e719bbc..71453eed81 100644
--- a/src/util/system.cpp
+++ b/src/util/system.cpp
@@ -5,9 +5,9 @@
#include <util/system.h>
-#ifdef HAVE_BOOST_PROCESS
+#ifdef ENABLE_EXTERNAL_SIGNER
#include <boost/process.hpp>
-#endif // HAVE_BOOST_PROCESS
+#endif // ENABLE_EXTERNAL_SIGNER
#include <chainparamsbase.h>
#include <sync.h>
@@ -1247,7 +1247,7 @@ void runCommand(const std::string& strCommand)
}
#endif
-#ifdef HAVE_BOOST_PROCESS
+#ifdef ENABLE_EXTERNAL_SIGNER
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in)
{
namespace bp = boost::process;
@@ -1282,7 +1282,7 @@ UniValue RunCommandParseJSON(const std::string& str_command, const std::string&
return result_json;
}
-#endif // HAVE_BOOST_PROCESS
+#endif // ENABLE_EXTERNAL_SIGNER
void SetupEnvironment()
{
diff --git a/src/util/system.h b/src/util/system.h
index 5959bc4196..de47b93b6e 100644
--- a/src/util/system.h
+++ b/src/util/system.h
@@ -108,7 +108,7 @@ std::string ShellEscape(const std::string& arg);
#if HAVE_SYSTEM
void runCommand(const std::string& strCommand);
#endif
-#ifdef HAVE_BOOST_PROCESS
+#ifdef ENABLE_EXTERNAL_SIGNER
/**
* Execute a command which returns JSON, and parse the result.
*
@@ -117,7 +117,7 @@ void runCommand(const std::string& strCommand);
* @return parsed JSON
*/
UniValue RunCommandParseJSON(const std::string& str_command, const std::string& str_std_in="");
-#endif // HAVE_BOOST_PROCESS
+#endif // ENABLE_EXTERNAL_SIGNER
/**
* Most paths passed as configuration arguments are treated as relative to
diff --git a/src/wallet/external_signer.cpp b/src/wallet/external_signer.cpp
new file mode 100644
index 0000000000..3396111760
--- /dev/null
+++ b/src/wallet/external_signer.cpp
@@ -0,0 +1,119 @@
+// Copyright (c) 2018-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparams.h>
+#include <core_io.h>
+#include <psbt.h>
+#include <util/strencodings.h>
+#include <util/system.h>
+#include <wallet/external_signer.h>
+
+ExternalSigner::ExternalSigner(const std::string& command, const std::string& fingerprint, std::string chain, std::string name): m_command(command), m_fingerprint(fingerprint), m_chain(chain), m_name(name) {}
+
+const std::string ExternalSigner::NetworkArg() const
+{
+ return " --chain " + m_chain;
+}
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+
+bool ExternalSigner::Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors)
+{
+ // Call <command> enumerate
+ const UniValue result = RunCommandParseJSON(command + " enumerate");
+ if (!result.isArray()) {
+ if (ignore_errors) return false;
+ throw ExternalSignerException(strprintf("'%s' received invalid response, expected array of signers", command));
+ }
+ for (UniValue signer : result.getValues()) {
+ // Check for error
+ const UniValue& error = find_value(signer, "error");
+ if (!error.isNull()) {
+ if (ignore_errors) return false;
+ if (!error.isStr()) {
+ throw ExternalSignerException(strprintf("'%s' error", command));
+ }
+ throw ExternalSignerException(strprintf("'%s' error: %s", command, error.getValStr()));
+ }
+ // Check if fingerprint is present
+ const UniValue& fingerprint = find_value(signer, "fingerprint");
+ if (fingerprint.isNull()) {
+ if (ignore_errors) return false;
+ throw ExternalSignerException(strprintf("'%s' received invalid response, missing signer fingerprint", command));
+ }
+ std::string fingerprintStr = fingerprint.get_str();
+ // Skip duplicate signer
+ bool duplicate = false;
+ for (ExternalSigner signer : signers) {
+ if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
+ }
+ if (duplicate) break;
+ std::string name = "";
+ const UniValue& model_field = find_value(signer, "model");
+ if (model_field.isStr() && model_field.getValStr() != "") {
+ name += model_field.getValStr();
+ }
+ signers.push_back(ExternalSigner(command, fingerprintStr, chain, name));
+ }
+ return true;
+}
+
+UniValue ExternalSigner::DisplayAddress(const std::string& descriptor) const
+{
+ return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " displayaddress --desc \"" + descriptor + "\"");
+}
+
+UniValue ExternalSigner::GetDescriptors(int account)
+{
+ return RunCommandParseJSON(m_command + " --fingerprint \"" + m_fingerprint + "\"" + NetworkArg() + " getdescriptors --account " + strprintf("%d", account));
+}
+
+bool ExternalSigner::SignTransaction(PartiallySignedTransaction& psbtx, std::string& error)
+{
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ // Check if signer fingerprint matches any input master key fingerprint
+ bool match = false;
+ for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
+ const PSBTInput& input = psbtx.inputs[i];
+ for (auto entry : input.hd_keypaths) {
+ if (m_fingerprint == strprintf("%08x", ReadBE32(entry.second.fingerprint))) match = true;
+ }
+ }
+
+ if (!match) {
+ error = "Signer fingerprint " + m_fingerprint + " does not match any of the inputs:\n" + EncodeBase64(ssTx.str());
+ return false;
+ }
+
+ std::string command = m_command + " --stdin --fingerprint \"" + m_fingerprint + "\"" + NetworkArg();
+ std::string stdinStr = "signtx \"" + EncodeBase64(ssTx.str()) + "\"";
+
+ const UniValue signer_result = RunCommandParseJSON(command, stdinStr);
+
+ if (find_value(signer_result, "error").isStr()) {
+ error = find_value(signer_result, "error").get_str();
+ return false;
+ }
+
+ if (!find_value(signer_result, "psbt").isStr()) {
+ error = "Unexpected result from signer";
+ return false;
+ }
+
+ PartiallySignedTransaction signer_psbtx;
+ std::string signer_psbt_error;
+ if (!DecodeBase64PSBT(signer_psbtx, find_value(signer_result, "psbt").get_str(), signer_psbt_error)) {
+ error = strprintf("TX decode failed %s", signer_psbt_error);
+ return false;
+ }
+
+ psbtx = signer_psbtx;
+
+ return true;
+}
+
+#endif
diff --git a/src/wallet/external_signer.h b/src/wallet/external_signer.h
new file mode 100644
index 0000000000..4b9711107b
--- /dev/null
+++ b/src/wallet/external_signer.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2018-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_EXTERNAL_SIGNER_H
+#define BITCOIN_WALLET_EXTERNAL_SIGNER_H
+
+#include <stdexcept>
+#include <string>
+#include <univalue.h>
+#include <util/system.h>
+
+struct PartiallySignedTransaction;
+
+class ExternalSignerException : public std::runtime_error {
+public:
+ using std::runtime_error::runtime_error;
+};
+
+//! Enables interaction with an external signing device or service, such as
+//! a hardware wallet. See doc/external-signer.md
+class ExternalSigner
+{
+private:
+ //! The command which handles interaction with the external signer.
+ std::string m_command;
+
+public:
+ //! @param[in] command the command which handles interaction with the external signer
+ //! @param[in] fingerprint master key fingerprint of the signer
+ //! @param[in] chain "main", "test", "regtest" or "signet"
+ //! @param[in] name device name
+ ExternalSigner(const std::string& command, const std::string& fingerprint, std::string chain, std::string name);
+
+ //! Master key fingerprint of the signer
+ std::string m_fingerprint;
+
+ //! Bitcoin mainnet, testnet, etc
+ std::string m_chain;
+
+ //! Name of signer
+ std::string m_name;
+
+ const std::string NetworkArg() const;
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+ //! Obtain a list of signers. Calls `<command> enumerate`.
+ //! @param[in] command the command which handles interaction with the external signer
+ //! @param[in,out] signers vector to which new signers (with a unique master key fingerprint) are added
+ //! @param chain "main", "test", "regtest" or "signet"
+ //! @param[out] success Boolean
+ static bool Enumerate(const std::string& command, std::vector<ExternalSigner>& signers, std::string chain, bool ignore_errors = false);
+
+ //! Display address on the device. Calls `<command> displayaddress --desc <descriptor>`.
+ //! @param[in] descriptor Descriptor specifying which address to display.
+ //! Must include a public key or xpub, as well as key origin.
+ UniValue DisplayAddress(const std::string& descriptor) const;
+
+ //! Get receive and change Descriptor(s) from device for a given account.
+ //! Calls `<command> getdescriptors --account <account>`
+ //! @param[in] account which BIP32 account to use (e.g. `m/44'/0'/account'`)
+ //! @param[out] UniValue see doc/external-signer.md
+ UniValue GetDescriptors(int account);
+
+ //! Sign PartiallySignedTransaction on the device.
+ //! Calls `<command> signtransaction` and passes the PSBT via stdin.
+ //! @param[in,out] psbt PartiallySignedTransaction to be signed
+ bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error);
+
+#endif
+};
+
+#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_H
diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp
new file mode 100644
index 0000000000..a2071e521a
--- /dev/null
+++ b/src/wallet/external_signer_scriptpubkeyman.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2020 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 <chainparams.h>
+#include <wallet/external_signer.h>
+#include <wallet/external_signer_scriptpubkeyman.h>
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+
+bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr<Descriptor> desc)
+{
+ LOCK(cs_desc_man);
+ assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
+ assert(m_storage.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
+
+ int64_t creation_time = GetTime();
+
+ // Make the descriptor
+ WalletDescriptor w_desc(std::move(desc), creation_time, 0, 0, 0);
+ m_wallet_descriptor = w_desc;
+
+ // Store the descriptor
+ WalletBatch batch(m_storage.GetDatabase());
+ if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
+ throw std::runtime_error(std::string(__func__) + ": writing descriptor failed");
+ }
+
+ // TopUp
+ TopUp();
+
+ m_storage.UnsetBlankWalletFlag(batch);
+ return true;
+}
+
+ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
+ const std::string command = gArgs.GetArg("-signer", "");
+ if (command == "") throw std::runtime_error(std::string(__func__) + ": restart bitcoind with -signer=<cmd>");
+ std::vector<ExternalSigner> signers;
+ ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+ if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
+ // TODO: add fingerprint argument in case of multiple signers
+ return signers[0];
+}
+
+bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const
+{
+ // TODO: avoid the need to infer a descriptor from inside a descriptor wallet
+ auto provider = GetSolvingProvider(scriptPubKey);
+ auto descriptor = InferDescriptor(scriptPubKey, *provider);
+
+ signer.DisplayAddress(descriptor->ToString());
+ // TODO inspect result
+ return true;
+}
+
+// If sign is true, transaction must previously have been filled
+TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
+{
+ if (!sign) {
+ return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed);
+ }
+
+ // Already complete if every input is now signed
+ bool complete = true;
+ for (const auto& input : psbt.inputs) {
+ // TODO: for multisig wallets, we should only care if all _our_ inputs are signed
+ complete &= PSBTInputSigned(input);
+ }
+ if (complete) return TransactionError::OK;
+
+ std::string strFailReason;
+ if(!GetExternalSigner().SignTransaction(psbt, strFailReason)) {
+ tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
+ return TransactionError::EXTERNAL_SIGNER_FAILED;
+ }
+ FinalizePSBT(psbt); // This won't work in a multisig setup
+ return TransactionError::OK;
+}
+
+#endif
diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h
new file mode 100644
index 0000000000..e60d7b8004
--- /dev/null
+++ b/src/wallet/external_signer_scriptpubkeyman.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2019-2020 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_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
+#define BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+#include <wallet/scriptpubkeyman.h>
+
+class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
+{
+ public:
+ ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor)
+ : DescriptorScriptPubKeyMan(storage, descriptor)
+ {}
+ ExternalSignerScriptPubKeyMan(WalletStorage& storage, bool internal)
+ : DescriptorScriptPubKeyMan(storage, internal)
+ {}
+
+ /** Provide a descriptor at setup time
+ * Returns false if already setup or setup fails, true if setup is successful
+ */
+ bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
+
+ static ExternalSigner GetExternalSigner();
+
+ bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
+
+ TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
+};
+#endif
+
+#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 0d2be64dfb..fc530ee286 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -61,6 +61,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const
argsman.AddArg("-paytxfee=<amt>", strprintf("Fee (in %s/kB) to add to transactions you send (default: %s)",
CURRENCY_UNIT, FormatMoney(CFeeRate{DEFAULT_PAY_TX_FEE}.GetFeePerK())), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-rescan", "Rescan the block chain for missing wallet transactions on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+#ifdef ENABLE_EXTERNAL_SIGNER
+ argsman.AddArg("-signer=<cmd>", "External signing tool, see docs/external-signer.md", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
+#endif
argsman.AddArg("-spendzeroconfchange", strprintf("Spend unconfirmed change when sending transactions (default: %u)", DEFAULT_SPEND_ZEROCONF_CHANGE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-txconfirmtarget=<n>", strprintf("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)", DEFAULT_TX_CONFIRM_TARGET), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
argsman.AddArg("-wallet=<path>", "Specify wallet path to load at startup. Can be used multiple times to load multiple wallets. Path is to a directory containing wallet data and log files. If the path is not absolute, it is interpreted relative to <walletdir>. This only loads existing wallets and does not create new ones. For backwards compatibility this also accepts names of existing top-level data files in <walletdir>.", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index e4e8c50f4f..1fb789b128 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -23,6 +23,7 @@
#include <wallet/fees.h>
#include <wallet/ismine.h>
#include <wallet/load.h>
+#include <wallet/rpcsigner.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>
@@ -518,6 +519,15 @@ public:
}, command.argNames, command.unique_id);
m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
}
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+ for (const CRPCCommand& command : GetSignerRPCCommands()) {
+ m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) {
+ return command.actor({request, m_context}, result, last_handler);
+ }, command.argNames, command.unique_id);
+ m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back()));
+ }
+#endif
}
bool verify() override { return VerifyWallets(*m_context.chain); }
bool load() override { return LoadWallets(*m_context.chain); }
diff --git a/src/wallet/rpcsigner.cpp b/src/wallet/rpcsigner.cpp
new file mode 100644
index 0000000000..607b778c68
--- /dev/null
+++ b/src/wallet/rpcsigner.cpp
@@ -0,0 +1,111 @@
+// Copyright (c) 2018-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <chainparamsbase.h>
+#include <key_io.h>
+#include <rpc/server.h>
+#include <rpc/util.h>
+#include <util/strencodings.h>
+#include <wallet/rpcsigner.h>
+#include <wallet/rpcwallet.h>
+#include <wallet/wallet.h>
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+
+static RPCHelpMan enumeratesigners()
+{
+ return RPCHelpMan{
+ "enumeratesigners",
+ "Returns a list of external signers from -signer.",
+ {},
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "signers", /* optional */ false, "",
+ {
+ {RPCResult::Type::STR_HEX, "masterkeyfingerprint", "Master key fingerprint"},
+ {RPCResult::Type::STR, "name", "Device name"},
+ },
+ }
+ }
+ },
+ RPCExamples{""},
+ [](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+
+ const std::string command = gArgs.GetArg("-signer", "");
+ if (command == "") throw JSONRPCError(RPC_WALLET_ERROR, "Error: restart bitcoind with -signer=<cmd>");
+ std::string chain = gArgs.GetChainName();
+ UniValue signers_res = UniValue::VARR;
+ try {
+ std::vector<ExternalSigner> signers;
+ ExternalSigner::Enumerate(command, signers, chain);
+ for (ExternalSigner signer : signers) {
+ UniValue signer_res = UniValue::VOBJ;
+ signer_res.pushKV("fingerprint", signer.m_fingerprint);
+ signer_res.pushKV("name", signer.m_name);
+ signers_res.push_back(signer_res);
+ }
+ } catch (const ExternalSignerException& e) {
+ throw JSONRPCError(RPC_WALLET_ERROR, e.what());
+ }
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("signers", signers_res);
+ return result;
+ }
+ };
+}
+
+static RPCHelpMan signerdisplayaddress()
+{
+ return RPCHelpMan{
+ "signerdisplayaddress",
+ "Display address on an external signer for verification.\n",
+ {
+ {"address", RPCArg::Type::STR, RPCArg::Optional::NO, /* default_val */ "", "bitcoin address to display"},
+ },
+ RPCResult{RPCResult::Type::NONE,"",""},
+ RPCExamples{""},
+ [](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return NullUniValue;
+ CWallet* const pwallet = wallet.get();
+
+ LOCK(pwallet->cs_wallet);
+
+ CTxDestination dest = DecodeDestination(request.params[0].get_str());
+
+ // Make sure the destination is valid
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
+ }
+
+ if (!pwallet->DisplayAddress(dest)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Failed to display address");
+ }
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("address", request.params[0].get_str());
+ return result;
+ }
+ };
+}
+
+Span<const CRPCCommand> GetSignerRPCCommands()
+{
+
+// clang-format off
+static const CRPCCommand commands[] =
+{ // category actor (function)
+ // --------------------- ------------------------
+ { "signer", &enumeratesigners, },
+ { "signer", &signerdisplayaddress, },
+};
+// clang-format on
+ return MakeSpan(commands);
+}
+
+
+#endif // ENABLE_EXTERNAL_SIGNER
diff --git a/src/wallet/rpcsigner.h b/src/wallet/rpcsigner.h
new file mode 100644
index 0000000000..f3ab83c428
--- /dev/null
+++ b/src/wallet/rpcsigner.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_WALLET_RPCSIGNER_H
+#define BITCOIN_WALLET_RPCSIGNER_H
+
+#include <span.h>
+#include <util/system.h>
+#include <vector>
+
+#ifdef ENABLE_EXTERNAL_SIGNER
+
+class CRPCCommand;
+
+namespace interfaces {
+class Chain;
+class Handler;
+}
+
+Span<const CRPCCommand> GetSignerRPCCommands();
+
+#endif // ENABLE_EXTERNAL_SIGNER
+
+#endif //BITCOIN_WALLET_RPCSIGNER_H
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 53232db6bc..bfc42ac1b0 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2726,6 +2726,7 @@ static RPCHelpMan createwallet()
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
{"descriptors", RPCArg::Type::BOOL, /* default */ "false", "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
{"load_on_startup", RPCArg::Type::BOOL, /* default */ "null", "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
+ {"external_signer", RPCArg::Type::BOOL, /* default */ "false", "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@@ -2770,6 +2771,13 @@ static RPCHelpMan createwallet()
flags |= WALLET_FLAG_DESCRIPTORS;
warnings.emplace_back(Untranslated("Wallet is an experimental descriptor wallet"));
}
+ if (!request.params[7].isNull() && request.params[7].get_bool()) {
+#ifdef ENABLE_EXTERNAL_SIGNER
+ flags |= WALLET_FLAG_EXTERNAL_SIGNER;
+#else
+ throw JSONRPCError(RPC_WALLET_ERROR, "Configure with --enable-external-signer to use this");
+#endif
+ }
#ifndef USE_BDB
if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
@@ -4187,8 +4195,10 @@ static RPCHelpMan send()
// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
- // Fill transaction with our data and sign
- bool complete = true;
+ // First fill transaction with our data without signing,
+ // so external signers are not asked sign more than once.
+ bool complete;
+ pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
if (err != TransactionError::OK) {
throw JSONRPCTransactionError(err);
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 4630603f8e..efb408c163 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -13,6 +13,7 @@
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
+#include <wallet/external_signer.h>
#include <wallet/scriptpubkeyman.h>
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 51283e791d..b8e34fbac3 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -517,8 +517,6 @@ public:
class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
{
private:
- WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man);
-
using ScriptPubKeyMap = std::map<CScript, int32_t>; // Map of scripts to descriptor range index
using PubKeyMap = std::map<CPubKey, int32_t>; // Map of pubkeys involved in scripts to descriptor range index
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
@@ -547,6 +545,9 @@ private:
// Fetch the SigningProvider for a given index and optionally include private keys. Called by the above functions.
std::unique_ptr<FlatSigningProvider> GetSigningProvider(int32_t index, bool include_private = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
+protected:
+ WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man);
+
public:
DescriptorScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor)
: ScriptPubKeyMan(storage),
@@ -581,6 +582,11 @@ public:
//! Setup descriptors based on the given CExtkey
bool SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type);
+ /** Provide a descriptor at setup time
+ * Returns false if already setup or setup fails, true if setup is successful
+ */
+ bool SetupDescriptor(std::unique_ptr<Descriptor>desc);
+
bool HavePrivateKeys() const override;
int64_t GetOldestKeyPoolTime() const override;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index db80745db0..08e480225d 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -14,10 +14,12 @@
#include <key.h>
#include <key_io.h>
#include <optional.h>
+#include <outputtype.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
+#include <psbt.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/signingprovider.h>
@@ -32,6 +34,7 @@
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
+#include <wallet/external_signer_scriptpubkeyman.h>
#include <univalue.h>
@@ -259,6 +262,20 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
}
+ // Private keys must be disabled for an external signer wallet
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ error = Untranslated("Private keys must be disabled when using an external signer");
+ status = DatabaseStatus::FAILED_CREATE;
+ return nullptr;
+ }
+
+ // Descriptor support must be enabled for an external signer wallet
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) && !(wallet_creation_flags & WALLET_FLAG_DESCRIPTORS)) {
+ error = Untranslated("Descriptor support must be enabled when using an external signer");
+ status = DatabaseStatus::FAILED_CREATE;
+ return nullptr;
+ }
+
// Wallet::Verify will check if we're trying to create a wallet with a duplicate name.
std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(name, options, status, error);
if (!database) {
@@ -3558,6 +3575,38 @@ void ReserveDestination::ReturnDestination()
address = CNoDestination();
}
+#ifdef ENABLE_EXTERNAL_SIGNER
+ExternalSigner CWallet::GetExternalSigner()
+{
+ const std::string command = gArgs.GetArg("-signer", "");
+ if (command == "") throw std::runtime_error(std::string(__func__) + ": restart bitcoind with -signer=<cmd>");
+ std::vector<ExternalSigner> signers;
+ ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+ if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
+ // TODO: add fingerprint argument in case of multiple signers
+ return signers[0];
+}
+#endif
+
+bool CWallet::DisplayAddress(const CTxDestination& dest)
+{
+#ifdef ENABLE_EXTERNAL_SIGNER
+ CScript scriptPubKey = GetScriptForDestination(dest);
+ const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
+ if (spk_man == nullptr) {
+ return false;
+ }
+ auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan*>(spk_man);
+ if (signer_spk_man == nullptr) {
+ return false;
+ }
+ ExternalSigner signer = GetExternalSigner(); // TODO: move signer in spk_man
+ return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+#else
+ return false;
+#endif
+}
+
void CWallet::LockCoin(const COutPoint& output)
{
AssertLockHeld(cs_wallet);
@@ -3836,7 +3885,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, const std::st
walletInstance->SetupLegacyScriptPubKeyMan();
}
- if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
+ if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) || !(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
LOCK(walletInstance->cs_wallet);
if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupDescriptorScriptPubKeyMans();
@@ -4443,40 +4492,82 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
- auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
- m_spk_managers[id] = std::move(spk_manager);
+ if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
+#ifdef ENABLE_EXTERNAL_SIGNER
+ auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc));
+ m_spk_managers[id] = std::move(spk_manager);
+#else
+ throw std::runtime_error(std::string(__func__) + ": Configure with --enable-external-signer to use external signer wallets");
+#endif
+ } else {
+ auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc));
+ m_spk_managers[id] = std::move(spk_manager);
+ }
}
void CWallet::SetupDescriptorScriptPubKeyMans()
{
AssertLockHeld(cs_wallet);
- // Make a seed
- CKey seed_key;
- seed_key.MakeNewKey(true);
- CPubKey seed = seed_key.GetPubKey();
- assert(seed_key.VerifyPubKey(seed));
+ if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
+ // Make a seed
+ CKey seed_key;
+ seed_key.MakeNewKey(true);
+ CPubKey seed = seed_key.GetPubKey();
+ assert(seed_key.VerifyPubKey(seed));
- // Get the extended key
- CExtKey master_key;
- master_key.SetSeed(seed_key.begin(), seed_key.size());
+ // Get the extended key
+ CExtKey master_key;
+ master_key.SetSeed(seed_key.begin(), seed_key.size());
- for (bool internal : {false, true}) {
- for (OutputType t : OUTPUT_TYPES) {
- auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
- if (IsCrypted()) {
- if (IsLocked()) {
- throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ for (bool internal : {false, true}) {
+ for (OutputType t : OUTPUT_TYPES) {
+ auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
+ if (IsCrypted()) {
+ if (IsLocked()) {
+ throw std::runtime_error(std::string(__func__) + ": Wallet is locked, cannot setup new descriptors");
+ }
+ if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
+ throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ }
}
- if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) {
- throw std::runtime_error(std::string(__func__) + ": Could not encrypt new descriptors");
+ spk_manager->SetupDescriptorGeneration(master_key, t);
+ uint256 id = spk_manager->GetID();
+ m_spk_managers[id] = std::move(spk_manager);
+ AddActiveScriptPubKeyMan(id, t, internal);
+ }
+ }
+ } else {
+#ifdef ENABLE_EXTERNAL_SIGNER
+ ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
+
+ // TODO: add account parameter
+ int account = 0;
+ UniValue signer_res = signer.GetDescriptors(account);
+
+ if (!signer_res.isObject()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
+ for (bool internal : {false, true}) {
+ const UniValue& descriptor_vals = find_value(signer_res, internal ? "internal" : "receive");
+ if (!descriptor_vals.isArray()) throw std::runtime_error(std::string(__func__) + ": Unexpected result");
+ for (const UniValue& desc_val : descriptor_vals.get_array().getValues()) {
+ std::string desc_str = desc_val.getValStr();
+ FlatSigningProvider keys;
+ std::string dummy_error;
+ std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, dummy_error, false);
+ if (!desc->GetOutputType()) {
+ continue;
}
+ OutputType t = *desc->GetOutputType();
+ auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, internal));
+ spk_manager->SetupDescriptor(std::move(desc));
+ uint256 id = spk_manager->GetID();
+ m_spk_managers[id] = std::move(spk_manager);
+ AddActiveScriptPubKeyMan(id, t, internal);
}
- spk_manager->SetupDescriptorGeneration(master_key, t);
- uint256 id = spk_manager->GetID();
- m_spk_managers[id] = std::move(spk_manager);
- AddActiveScriptPubKeyMan(id, t, internal);
}
+#else
+ throw std::runtime_error(std::string(__func__) + ": Wallets with external signers require Boost::Process library.");
+#endif
}
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 4fc4466604..eb797938cd 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -22,6 +22,7 @@
#include <wallet/coinselection.h>
#include <wallet/crypter.h>
#include <wallet/scriptpubkeyman.h>
+#include <wallet/external_signer.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
@@ -95,7 +96,6 @@ constexpr CAmount DEFAULT_TRANSACTION_MAXFEE{COIN / 10};
constexpr CAmount HIGH_TX_FEE_PER_KB{COIN / 100};
//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis)
constexpr CAmount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB};
-
//! Pre-calculated constants for input size estimation in *virtual size*
static constexpr size_t DUMMY_NESTED_P2WPKH_INPUT_SIZE = 91;
@@ -115,7 +115,8 @@ static constexpr uint64_t KNOWN_WALLET_FLAGS =
| WALLET_FLAG_BLANK_WALLET
| WALLET_FLAG_KEY_ORIGIN_METADATA
| WALLET_FLAG_DISABLE_PRIVATE_KEYS
- | WALLET_FLAG_DESCRIPTORS;
+ | WALLET_FLAG_DESCRIPTORS
+ | WALLET_FLAG_EXTERNAL_SIGNER;
static constexpr uint64_t MUTABLE_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE;
@@ -126,6 +127,7 @@ static const std::map<std::string,WalletFlags> WALLET_FLAG_MAP{
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
{"descriptor_wallet", WALLET_FLAG_DESCRIPTORS},
+ {"external_signer", WALLET_FLAG_EXTERNAL_SIGNER}
};
extern const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS;
@@ -837,6 +839,12 @@ public:
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool separate_coins, const CFeeRate& effective_feerate, const CFeeRate& long_term_feerate, const CoinEligibilityFilter& filter, bool positive_only) const;
+#ifdef ENABLE_EXTERNAL_SIGNER
+ ExternalSigner GetExternalSigner() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+#endif
+ /** Display address on an external signer. Returns false if external signer support is not compiled */
+ bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
bool IsLockedCoin(uint256 hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UnlockCoin(const COutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h
index 67b2ee2b98..0713f768c1 100644
--- a/src/wallet/walletutil.h
+++ b/src/wallet/walletutil.h
@@ -60,6 +60,9 @@ enum WalletFlags : uint64_t {
//! Indicate that this wallet supports DescriptorScriptPubKeyMan
WALLET_FLAG_DESCRIPTORS = (1ULL << 34),
+
+ //! Indicates that the wallet needs an external signer
+ WALLET_FLAG_EXTERNAL_SIGNER = (1ULL << 35),
};
//! Get the path of the wallet directory.