From f8dac8e2b7f920ba67077480ad10e36a2ca76c0b Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 20 Oct 2025 14:42:48 +0700 Subject: [PATCH 01/23] fix: missing dashification of rpc/backup.cpp --- src/wallet/rpc/backup.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 055aceff5ea73..0802f0d46a17f 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -1954,7 +1954,7 @@ RPCHelpMan importdescriptors() { "block from time %d, which is after or within %d seconds of key creation, and " "could contain transactions pertaining to the desc. As a result, transactions " "and coins using this desc may not appear in the wallet. This error could be " - "caused by pruning or data corruption (see bitcoind log for details) and could " + "caused by pruning or data corruption (see dashd log for details) and could " "be dealt with by downloading and rescanning the relevant blocks (see -reindex " "and -rescan options).", GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); From 78e9c97cb4eaf49c20ec1cdc3cd01983dbfc8ade Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 1 Mar 2025 00:47:46 +0700 Subject: [PATCH 02/23] fix: remove nodiscard attribute as follow-up for bitcoin#18115 --- src/wallet/wallet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8e43ce6d63bae..600924940819c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -687,7 +687,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati * @param[in] finalize whether to create the final scriptSig * return error */ - [[nodiscard]] TransactionError FillPSBT(PartiallySignedTransaction& psbtx, + TransactionError FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, From ac316756c94a0f6ee644b4b08be2cfa7f6f8823a Mon Sep 17 00:00:00 2001 From: "Wladimir J. van der Laan" Date: Tue, 23 Feb 2021 17:56:24 +0100 Subject: [PATCH 03/23] 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=`, 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 for a list of signers (e.g. devices) and their master key fingerprint * `signerdisplayaddress
`: asks 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 --- configure.ac | 30 +-- doc/Doxyfile.in | 2 +- doc/external-signer.md | 171 +++++++++++++++ src/Makefile.am | 6 + src/dummywallet.cpp | 1 + src/rpc/client.cpp | 1 + src/test/system_tests.cpp | 10 +- src/util/error.cpp | 4 + src/util/error.h | 2 + src/util/system.cpp | 8 +- src/util/system.h | 4 +- src/wallet/external_signer.cpp | 119 ++++++++++ src/wallet/external_signer.h | 73 +++++++ .../external_signer_scriptpubkeyman.cpp | 83 +++++++ src/wallet/external_signer_scriptpubkeyman.h | 36 ++++ src/wallet/init.cpp | 3 + src/wallet/interfaces.cpp | 12 ++ src/wallet/rpc/spend.cpp | 6 +- src/wallet/rpc/wallet.cpp | 8 + src/wallet/rpcsigner.cpp | 108 ++++++++++ src/wallet/rpcsigner.h | 26 +++ src/wallet/scriptpubkeyman.cpp | 1 + src/wallet/scriptpubkeyman.h | 10 +- src/wallet/wallet.cpp | 95 +++++++- src/wallet/wallet.h | 12 +- src/wallet/walletutil.h | 3 + test/config.ini.in | 1 + test/functional/mocks/signer.py | 102 +++++++++ test/functional/rpc_help.py | 5 +- .../test_framework/test_framework.py | 9 + test/functional/test_framework/test_node.py | 4 +- test/functional/test_runner.py | 1 + test/functional/wallet_signer.py | 203 ++++++++++++++++++ 33 files changed, 1122 insertions(+), 37 deletions(-) create mode 100644 doc/external-signer.md create mode 100644 src/wallet/external_signer.cpp create mode 100644 src/wallet/external_signer.h create mode 100644 src/wallet/external_signer_scriptpubkeyman.cpp create mode 100644 src/wallet/external_signer_scriptpubkeyman.h create mode 100644 src/wallet/rpcsigner.cpp create mode 100644 src/wallet/rpcsigner.h create mode 100755 test/functional/mocks/signer.py create mode 100755 test/functional/wallet_signer.py diff --git a/configure.ac b/configure.ac index dec5c94755c32..a59732f75a5eb 100644 --- a/configure.ac +++ b/configure.ac @@ -325,10 +325,10 @@ AC_ARG_ENABLE([werror], [enable_werror=$enableval], [enable_werror=no]) -AC_ARG_WITH([boost-process], - [AS_HELP_STRING([--with-boost-process],[Opt in to using Boost Process (default is no)])], - [boost_process=$withval], - [boost_process=no]) +AC_ARG_ENABLE([external-signer], + [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is no, requires Boost::Process)])], + [use_external_signer=$enableval], + [use_external_signer=no]) AC_LANG_PUSH([C++]) @@ -1412,6 +1412,7 @@ if test "$enable_fuzz" = "yes"; then bitcoin_enable_qt_dbus=no use_bench=no use_tests=no + use_external_signer=no use_upnp=no use_natpmp=no use_zmq=no @@ -1573,20 +1574,22 @@ dnl See: https://github.com/boostorg/config/pull/430. AX_CHECK_PREPROC_FLAG([-DBOOST_NO_CXX98_FUNCTION_BASE], [BOOST_CPPFLAGS="$BOOST_CPPFLAGS -DBOOST_NO_CXX98_FUNCTION_BASE"], [], [$CXXFLAG_WERROR], [AC_LANG_PROGRAM([[#include ]])]) -dnl Opt-in to Boost Process -if test "$boost_process" != "no"; then +dnl Opt-in to Boost Process if external signer support is requested +if test "$use_external_signer" != no; then AC_MSG_CHECKING(for Boost Process) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ boost::process::child* child = new boost::process::child; delete child; ]])], - [ AC_MSG_RESULT(yes); AC_DEFINE([HAVE_BOOST_PROCESS],,[define if Boost::Process is available])], - [ AC_MSG_ERROR([Boost::Process is not available!])] + [ AC_MSG_RESULT(yes) + AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled]) + ], + [ AC_MSG_ERROR([Boost::Process is required for external signer support, but not available!])] ) fi - if test "$suppress_external_warnings" != "no"; then +AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = "yes"]) + BOOST_CPPFLAGS=SUPPRESS_WARNINGS($BOOST_CPPFLAGS) fi -fi dnl Check for reduced exports if test "$use_reduce_exports" = "yes"; then @@ -1974,6 +1977,7 @@ AC_SUBST(ARM_SHANI_CXXFLAGS) AC_SUBST(LIBTOOL_APP_LDFLAGS) AC_SUBST(USE_SQLITE) AC_SUBST(USE_BDB) +AC_SUBST(ENABLE_EXTERNAL_SIGNER) AC_SUBST(USE_UPNP) AC_SUBST(USE_QRCODE) AC_SUBST(TESTDEFS) @@ -2038,7 +2042,7 @@ esac echo echo "Options used to compile and link:" -echo " boost process = $with_boost_process" +echo " external signer = $use_external_signer" echo " multiprocess = $build_multiprocess" echo " with libs = $build_bitcoin_libs" echo " with wallet = $enable_wallet" @@ -2048,13 +2052,13 @@ if test "$enable_wallet" != "no"; then fi echo " with gui / qt = $bitcoin_enable_qt" if test $bitcoin_enable_qt != "no"; then - echo " with qr = $use_qr" + echo " with qr = $use_qr" fi echo " with zmq = $use_zmq" if test $enable_fuzz = "no"; then echo " with test = $use_tests" else - echo " with test = not building test_dash because fuzzing is enabled" + echo " with test = not building test_dash because fuzzing is enabled" fi echo " with fuzz binary = $enable_fuzz_binary" echo " with bench = $use_bench" diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 8d54cb41ef7ef..611e4941a07ef 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -2093,7 +2093,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = HAVE_BOOST_PROCESS +PREDEFINED = ENABLE_EXTERNAL_SIGNER # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/doc/external-signer.md b/doc/external-signer.md new file mode 100644 index 0000000000000..053752ee2f163 --- /dev/null +++ b/doc/external-signer.md @@ -0,0 +1,171 @@ +# Support for signing transactions outside of Bitcoin Core + +Bitcoin Core can be launched with `-signer=` where `` is an external tool which can sign transactions and perform other functions. For example, it can be used to communicate with a hardware wallet. + +## Example usage + +The following example is based on the [HWI](https://github.com/bitcoin-core/HWI) tool. Although this tool is hosted under the Bitcoin Core GitHub organization and maintained by Bitcoin Core developers, it should be used with caution. It is considered experimental and has far less review than Bitcoin Core itself. Be particularly careful when running tools such as these on a computer with private keys on it. + +When using a hardware wallet, consult the manufacturer website for (alternative) software they recommend. As long as their software conforms to the standard below, it should be able to work with Bitcoin Core. + +Start Bitcoin Core: + +```sh +$ bitcoind -signer=../HWI/hwi.py +``` + +### Device setup + +Follow the hardware manufacturers instructions for the initial device setup, as well as their instructions for creating a backup. Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/bitcoin-core/HWI). + +### Create wallet and import keys + +Get a list of signing devices / services: + +``` +$ bitcoin-cli enumeratesigners +{ + "signers": [ + { + "fingerprint": "c8df832a" + } +] +``` + +The master key fingerprint is used to identify a device. + +Create a wallet, this automatically imports the public keys: + +```sh +$ bitcoin-cli createwallet "hww" true true "" true true true +``` + +### Verify an address + +Display an address on the device: + +```sh +$ bitcoin-cli -rpcwallet= getnewaddress +$ bitcoin-cli -rpcwallet= signerdisplayaddress
+``` + +Replace `
` with the result of `getnewaddress`. + +### Spending + +Under the hood this uses a [Partially Signed Bitcoin Transaction](psbt.md). + +```sh +$ bitcoin-cli -rpcwallet= sendtoaddress
+``` + +This prompts your hardware wallet to sign, and fail if it's not connected. If successful +it automatically broadcasts the transaction. + +```sh +{"complete": true, "txid": } +``` + +## Signer API + +In order to be compatible with Bitcoin Core any signer command should conform to the specification below. This specification is subject to change. Ideally a BIP should propose a standard so that other wallets can also make use of it. + +Prerequisite knowledge: +* [Output Descriptors](descriptors.md) +* Partially Signed Bitcoin Transaction ([PSBT](psbt.md)) + +### `enumerate` (required) + +Usage: +``` +$ enumerate +[ + { + "fingerprint": "00000000" + } +] +``` + +The command MUST return an (empty) array with at least a `fingerprint` field. + +A future extension could add an optional return field with device capabilities. Perhaps a descriptor with wildcards. For example: `["pkh("44'/0'/$'/{0,1}/*"), sh(wpkh("49'/0'/$'/{0,1}/*")), wpkh("84'/0'/$'/{0,1}/*")]`. This would indicate the device supports legacy, wrapped SegWit and native SegWit. In addition it restricts the derivation paths that can used for those, to maintain compatibility with other wallet software. It also indicates the device, or the driver, doesn't support multisig. + +A future extension could add an optional return field `reachable`, in case `` knows a signer exists but can't currently reach it. + +### `signtransaction` (required) + +Usage: +``` +$ --fingerprint= (--testnet) signtransaction +base64_encode_signed_psbt +``` + +The command returns a psbt with any signatures. + +The `psbt` SHOULD include bip32 derivations. The command SHOULD fail if none of the bip32 derivations match a key owned by the device. + +The command SHOULD fail if the user cancels. + +The command MAY complain if `--testnet` is set, but any of the BIP32 derivation paths contain a coin type other than `1h` (and vice versa). + +### `getdescriptors` (optional) + +Usage: + +``` +$ --fingerprint= (--testnet) getdescriptors + +``` + +Returns descriptors supported by the device. Example: + +``` +$ --fingerprint=00000000 --testnet getdescriptors +{ + "receive": [ + "pkh([00000000/44h/0h/0h]xpub6C.../0/*)#fn95jwmg", + "sh(wpkh([00000000/49h/0h/0h]xpub6B..../0/*))#j4r9hntt", + "wpkh([00000000/84h/0h/0h]xpub6C.../0/*)#qw72dxa9" + ], + "internal": [ + "pkh([00000000/44h/0h/0h]xpub6C.../1/*)#c8q40mts", + "sh(wpkh([00000000/49h/0h/0h]xpub6B..../1/*))#85dn0v75", + "wpkh([00000000/84h/0h/0h]xpub6C..../1/*)#36mtsnda" + ] +} +``` + +### `displayaddress` (optional) + +Usage: +``` + --fingerprint= (--testnet) displayaddress --desc descriptor +``` + +Example, display the first native SegWit receive address on Testnet: + +``` + --fingerprint=00000000 --testnet displayaddress --desc "wpkh([00000000/84h/1h/0h]tpubDDUZ..../0/0)" +``` + +The command MUST be able to figure out the address type from the descriptor. + +If contains a master key fingerprint, the command MUST fail if it does not match the fingerprint known by the device. + +If contains an xpub, the command MUST fail if it does not match the xpub known by the device. + +The command MAY complain if `--testnet` is set, but the BIP32 coin type is not `1h` (and vice versa). + +## How Bitcoin Core uses the Signer API + +The `enumeratesigners` RPC simply calls ` enumerate`. + +The `createwallet` RPC calls: + +* ` --fingerprint=00000000 getdescriptors 0` + +It then imports descriptors for all support address types, in a BIP44/49/84 compatible manner. + +The `displayaddress` RPC reuses some code from `getaddressinfo` on the provided address and obtains the inferred descriptor. It then calls ` --fingerprint=00000000 displayaddress --desc=`. + +`sendtoaddress` and `sendmany` check `inputs->bip32_derivs` to see if any inputs have the same `master_fingerprint` as the signer. If so, it calls ` --fingerprint=00000000 signtransaction `. It waits for the device to return a (partially) signed psbt, tries to finalize it and broadcasts the transation. diff --git a/src/Makefile.am b/src/Makefile.am index 7b5c4ca684028..23306b15c70a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -435,11 +435,14 @@ BITCOIN_CORE_H = \ wallet/crypter.h \ wallet/db.h \ wallet/dump.h \ + wallet/external_signer.h \ + wallet/external_signer_scriptpubkeyman.h \ wallet/fees.h \ wallet/hdchain.h \ wallet/ismine.h \ wallet/load.h \ wallet/receive.h \ + wallet/rpcsigner.h \ wallet/rpc/util.h \ wallet/rpc/wallet.h \ wallet/salvage.h \ @@ -647,6 +650,8 @@ libbitcoin_wallet_a_SOURCES = \ wallet/crypter.cpp \ wallet/db.cpp \ wallet/dump.cpp \ + wallet/external_signer_scriptpubkeyman.cpp \ + wallet/external_signer.cpp \ wallet/fees.cpp \ wallet/hdchain.cpp \ wallet/interfaces.cpp \ @@ -661,6 +666,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/rpc/transactions.cpp \ wallet/rpc/util.cpp \ wallet/rpc/wallet.cpp \ + wallet/rpcsigner.cpp \ wallet/scriptpubkeyman.cpp \ wallet/spend.cpp \ wallet/transaction.cpp \ diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index e02c4b505149d..5ad3b2ee23ee2 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -51,6 +51,7 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const "-maxtxfee=", "-rescan=", "-salvagewallet", + "-signer=", "-spendzeroconfchange", "-wallet=", "-walletbackupsdir=", diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 92378a3455a78..c7efe00245864 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -238,6 +238,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createwallet", 4, "avoid_reuse"}, { "createwallet", 5, "descriptors"}, { "createwallet", 6, "load_on_startup"}, + { "createwallet", 7, "external_signer"}, { "restorewallet", 2, "load_on_startup"}, { "loadwallet", 1, "load_on_startup"}, { "unloadwallet", 1, "load_on_startup"}, diff --git a/src/test/system_tests.cpp b/src/test/system_tests.cpp index deef4f12b1b19..51ec01a4aa7b0 100644 --- a/src/test/system_tests.cpp +++ b/src/test/system_tests.cpp @@ -6,22 +6,22 @@ #include #include -#ifdef HAVE_BOOST_PROCESS +#ifdef ENABLE_EXTERNAL_SIGNER #include -#endif // HAVE_BOOST_PROCESS +#endif // ENABLE_EXTERNAL_SIGNER #include 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 BOOST_AUTO_TEST_CASE(run_command) { @@ -96,6 +96,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 5dc2a89b30d18..9a6002209e3e9 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -33,6 +33,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 4cb519adf193c..42f19cf3cb21b 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 95dfa9c6511cf..38ba135fed397 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -8,9 +8,9 @@ #include -#ifdef HAVE_BOOST_PROCESS +#ifdef ENABLE_EXTERNAL_SIGNER #include -#endif // HAVE_BOOST_PROCESS +#endif // ENABLE_EXTERNAL_SIGNER #include #include @@ -1389,7 +1389,7 @@ void RenameThreadPool(ctpl::thread_pool& tp, const char* baseName) } } -#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; @@ -1424,7 +1424,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 53675ccf4cb89..1eb3bfebcfa0e 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -113,7 +113,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. * @@ -122,7 +122,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 0000000000000..61af489c379aa --- /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 +#include +#include +#include +#include +#include + +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) {} + +std::string ExternalSigner::NetworkArg() const +{ + return " --chain " + m_chain; +} + +#ifdef ENABLE_EXTERNAL_SIGNER + +bool ExternalSigner::Enumerate(const std::string& command, std::vector& signers, std::string chain, bool ignore_errors) +{ + // Call 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 = signer.find_value("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 = signer.find_value("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 = signer.find_value("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 (signer_result.find_value("error").isStr()) { + error = signer_result.find_value("error").get_str(); + return false; + } + + if (!signer_result.find_value("psbt").isStr()) { + error = "Unexpected result from signer"; + return false; + } + + PartiallySignedTransaction signer_psbtx; + std::string signer_psbt_error; + if (!DecodeBase64PSBT(signer_psbtx, signer_result.find_value("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 0000000000000..cc03d51cfcada --- /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 +#include +#include +#include + +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; + + //! Dash mainnet, testnet, etc + std::string m_chain; + + //! Name of signer + std::string m_name; + + std::string NetworkArg() const; + +#ifdef ENABLE_EXTERNAL_SIGNER + //! Obtain a list of signers. Calls ` 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& signers, std::string chain, bool ignore_errors = false); + + //! Display address on the device. Calls ` displayaddress --desc `. + //! @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 ` getdescriptors --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 ` 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 0000000000000..7e83485182df9 --- /dev/null +++ b/src/wallet/external_signer_scriptpubkeyman.cpp @@ -0,0 +1,83 @@ +// 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 +#include +#include + +#ifdef ENABLE_EXTERNAL_SIGNER + +namespace wallet { +bool ExternalSignerScriptPubKeyMan::SetupDescriptor(std::unique_ptr 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 dashd with -signer="); + std::vector 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, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const +{ + if (!sign) { + return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize); + } + + // 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; + } + if (finalize) FinalizePSBT(psbt); // This won't work in a multisig setup + return TransactionError::OK; +} +} // namespace wallet + +#endif diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h new file mode 100644 index 0000000000000..ac3adaa45a37e --- /dev/null +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -0,0 +1,36 @@ +// 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 + +namespace wallet { +class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan +{ + public: + ExternalSignerScriptPubKeyMan(WalletStorage& storage, WalletDescriptor& descriptor) + : DescriptorScriptPubKeyMan(storage, descriptor) + {} + ExternalSignerScriptPubKeyMan(WalletStorage& storage) + : DescriptorScriptPubKeyMan(storage) + {} + + /** Provide a descriptor at setup time + * Returns false if already setup or setup fails, true if setup is successful + */ + bool SetupDescriptor(std::unique_ptrdesc); + + static ExternalSigner GetExternalSigner(); + + bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const; + + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override; +}; +} // namespace wallet +#endif + +#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 4aa657beb8d3c..9cebf2b2e43d2 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -67,6 +67,9 @@ void WalletInit::AddWalletOptions(ArgsManager& argsman) const argsman.AddArg("-keypool=", strprintf("Set key pool size to (default: %u). Warning: Smaller sizes may increase the risk of losing funds when restoring from an old backup, if none of the addresses in the original keypool have been used.", DEFAULT_KEYPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); argsman.AddArg("-rescan=", "Rescan the block chain for missing wallet transactions on startup" " (1 = start from wallet creation time, 2 = start from genesis block)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); +#ifdef ENABLE_EXTERNAL_SIGNER + argsman.AddArg("-signer=", "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("-wallet=", "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 . 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 .", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); argsman.AddArg("-walletbackupsdir=", "Specify full path to directory for automatic wallet backups (must exist)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 4c134c76220ea..b62fb49f73784 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -601,6 +602,17 @@ class WalletLoaderImpl : public WalletLoader }, 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) { + JSONRPCRequest wallet_request = request; + wallet_request.context = m_context; + return command.actor(wallet_request, result, last_handler); + }, command.argNames, command.unique_id); + m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); + } +#endif } public: diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index 309da88bff3a1..54f0fb1a9f83a 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -904,8 +904,10 @@ 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; + (void)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/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index cdc91d4519137..73ff15a7373e0 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -623,6 +623,7 @@ static RPCHelpMan createwallet() {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."}, {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation. This feature is well-tested but still considered experimental."}, {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "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, RPCArg::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, "", "", @@ -672,6 +673,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)) { diff --git a/src/wallet/rpcsigner.cpp b/src/wallet/rpcsigner.cpp new file mode 100644 index 0000000000000..4f1a1bbe16c88 --- /dev/null +++ b/src/wallet/rpcsigner.cpp @@ -0,0 +1,108 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_EXTERNAL_SIGNER + +namespace wallet { +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 const wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + + const std::string command = gArgs.GetArg("-signer", ""); + if (command == "") throw JSONRPCError(RPC_WALLET_ERROR, "Error: restart dashd with -signer="); + std::string chain = gArgs.GetChainName(); + UniValue signers_res = UniValue::VARR; + try { + std::vector 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, "dash address to display"}, + }, + RPCResult{RPCResult::Type::NONE,"",""}, + RPCExamples{""}, + [](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { + std::shared_ptr 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 GetSignerRPCCommands() +{ + static const CRPCCommand commands[]{ + {"signer", &enumeratesigners}, + {"signer", &signerdisplayaddress}, + }; + return commands; +} +} // namespace wallet + + +#endif // ENABLE_EXTERNAL_SIGNER diff --git a/src/wallet/rpcsigner.h b/src/wallet/rpcsigner.h new file mode 100644 index 0000000000000..ccd2e993a8d3e --- /dev/null +++ b/src/wallet/rpcsigner.h @@ -0,0 +1,26 @@ +// 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 +#include +#include + +#ifdef ENABLE_EXTERNAL_SIGNER + +class CRPCCommand; + +namespace interfaces { +class Chain; +class Handler; +} +namespace wallet { +Span GetSignerRPCCommands(); +} // namespace + +#endif // ENABLE_EXTERNAL_SIGNER + +#endif //BITCOIN_WALLET_RPCSIGNER_H diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 28c354dd69979..941dc0f366a5a 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace wallet { diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 7f4ee75c413bc..b30b1bd6c5542 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -512,8 +512,6 @@ class LegacySigningProvider : public SigningProvider class DescriptorScriptPubKeyMan : public ScriptPubKeyMan { private: - WalletDescriptor m_wallet_descriptor GUARDED_BY(cs_desc_man); - using ScriptPubKeyMap = std::map; // Map of scripts to descriptor range index using PubKeyMap = std::map; // Map of pubkeys involved in scripts to descriptor range index using CryptedKeyMap = std::map>>; @@ -550,6 +548,9 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan // Fetch the SigningProvider for a given index and optionally include private keys. Called by the above functions. std::unique_ptr 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), @@ -584,6 +585,11 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan //! Setup descriptors based on the given CExtkey bool SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, PathDerivationType type); + /** Provide a descriptor at setup time + * Returns false if already setup or setup fails, true if setup is successful + */ + bool SetupDescriptor(std::unique_ptrdesc); + bool HavePrivateKeys() const override; std::optional GetOldestKeyPoolTime() const override; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1caf30747a3b0..ea4d96f56d1ab 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include