aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@gmail.com>2018-07-20 14:25:35 +0200
committerWladimir J. van der Laan <laanwj@gmail.com>2018-07-20 14:28:50 +0200
commit6b6e854362bc17c38c99a0b9215b60aed0e5fedc (patch)
tree25189039f74bc87f9ff50dd576f626088d6d3e3d
parentaba2e666d7fd42f40c0bf4196dbff66a48eaf97b (diff)
parenta3fa4d6a6acf19d640a1d5879a00aa1f059e2380 (diff)
downloadbitcoin-6b6e854362bc17c38c99a0b9215b60aed0e5fedc.tar.xz
Merge #9662: Add createwallet "disableprivatekeys" option: a sane mode for watchonly-wallets
a3fa4d6a6acf19d640a1d5879a00aa1f059e2380 QA: Fix bug in -usecli logic that converts booleans to non-lowercase strings (Jonas Schnelli) 4704e5f074e57782d058404a594a7313cf170cf0 [QA] add createwallet disableprivatekey test (Jonas Schnelli) c7b8f343e99d9d53ea353ddce9a977f1886caf30 [Qt] Disable creating receive addresses when private keys are disabled (Jonas Schnelli) 2f15c2bc20d583b4c1788da78c9c635c36e03ed0 Add disable privatekeys option to createwallet (Jonas Schnelli) cebefba0855cee7fbcb9474b34e6779369e8e9ce Add option to disable private keys during internal wallet creation (Jonas Schnelli) 9995a602a639b64a749545b7c3bafbf67f97324f Add facility to store wallet flags (64 bits) (Jonas Schnelli) Pull request description: This mode ('createwallet {"disableprivatekeys": true}') is intended for a sane pure watch-only mode, ideal for a use-case where one likes to use Bitcoin-Core in conjunction with a hardware-wallet or another solutions for cold-storage. Since we have support for custom change addresses in `fundrawtransaction`, pure watch-only wallets including coin-selection are possible and do make sense for some use cases. This new mode disables all forms of private key generation and ensure that no mix between hot and cold keys are possible. Tree-SHA512: 3ebe7e8d54c4d4e5f790c348d4c292d456f573960a5b04d69ca5ef43a9217c7e7671761c6968cdc56f9a8bc235f3badd358576651af9f10855a0eb731f3fc508
-rw-r--r--src/interfaces/wallet.cpp1
-rw-r--r--src/interfaces/wallet.h3
-rw-r--r--src/qt/receivecoinsdialog.cpp3
-rw-r--r--src/qt/walletmodel.cpp5
-rw-r--r--src/qt/walletmodel.h1
-rw-r--r--src/rpc/client.cpp1
-rw-r--r--src/wallet/rpcwallet.cpp56
-rw-r--r--src/wallet/test/wallet_tests.cpp9
-rw-r--r--src/wallet/wallet.cpp73
-rw-r--r--src/wallet/wallet.h23
-rw-r--r--src/wallet/walletdb.cpp22
-rw-r--r--src/wallet/walletdb.h1
-rwxr-xr-xtest/functional/test_framework/test_node.py3
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/wallet_disableprivatekeys.py32
15 files changed, 203 insertions, 32 deletions
diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp
index b42c9b63df..28081c7c2c 100644
--- a/src/interfaces/wallet.cpp
+++ b/src/interfaces/wallet.cpp
@@ -426,6 +426,7 @@ public:
}
unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; }
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
+ bool IsWalletFlagSet(uint64_t flag) override { return m_wallet.IsWalletFlagSet(flag); }
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 96e742eaca..ae54d42442 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -236,6 +236,9 @@ public:
// Return whether HD enabled.
virtual bool hdEnabled() = 0;
+ // check if a certain wallet flag is set.
+ virtual bool IsWalletFlagSet(uint64_t flag) = 0;
+
// Get default address type.
virtual OutputType getDefaultAddressType() = 0;
diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp
index e458a52856..8c430e0af1 100644
--- a/src/qt/receivecoinsdialog.cpp
+++ b/src/qt/receivecoinsdialog.cpp
@@ -99,6 +99,9 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model)
} else {
ui->useBech32->setCheckState(Qt::Unchecked);
}
+
+ // eventually disable the main receive button if private key operations are disabled
+ ui->receiveButton->setEnabled(!model->privateKeysDisabled());
}
}
diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp
index 389acf0a95..cd55b40b71 100644
--- a/src/qt/walletmodel.cpp
+++ b/src/qt/walletmodel.cpp
@@ -558,6 +558,11 @@ bool WalletModel::isWalletEnabled()
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
}
+bool WalletModel::privateKeysDisabled() const
+{
+ return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+}
+
QString WalletModel::getWalletName() const
{
return QString::fromStdString(m_wallet->getWalletName());
diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h
index 35ededb121..d8935c2fa8 100644
--- a/src/qt/walletmodel.h
+++ b/src/qt/walletmodel.h
@@ -197,6 +197,7 @@ public:
bool bumpFee(uint256 hash);
static bool isWalletEnabled();
+ bool privateKeysDisabled() const;
interfaces::Node& node() const { return m_node; }
interfaces::Wallet& wallet() const { return *m_wallet; }
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp
index 593c59ca5e..eb6b164075 100644
--- a/src/rpc/client.cpp
+++ b/src/rpc/client.cpp
@@ -172,6 +172,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "echojson", 9, "arg9" },
{ "rescanblockchain", 0, "start_height"},
{ "rescanblockchain", 1, "stop_height"},
+ { "createwallet", 1, "disable_private_keys"},
};
class CRPCConvertTable
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 02cdd0f9f1..73dfebf114 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -161,6 +161,10 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
+ HelpExampleRpc("getnewaddress", "")
);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
LOCK2(cs_main, pwallet->cs_wallet);
// Parse the label first so we don't generate a key if there's an error
@@ -268,6 +272,10 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
+ HelpExampleRpc("getrawchangeaddress", "")
);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
LOCK2(cs_main, pwallet->cs_wallet);
if (!pwallet->IsLocked()) {
@@ -2506,6 +2514,10 @@ static UniValue keypoolrefill(const JSONRPCRequest& request)
+ HelpExampleRpc("keypoolrefill", "")
);
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
+ }
+
LOCK2(cs_main, pwallet->cs_wallet);
// 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
@@ -2990,19 +3002,20 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
"Returns an object containing various wallet state info.\n"
"\nResult:\n"
"{\n"
- " \"walletname\": xxxxx, (string) the wallet name\n"
- " \"walletversion\": xxxxx, (numeric) the wallet version\n"
- " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
- " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
- " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
- " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
- " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
- " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
- " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
- " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
- " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n"
+ " \"walletname\": xxxxx, (string) the wallet name\n"
+ " \"walletversion\": xxxxx, (numeric) the wallet version\n"
+ " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
+ " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
+ " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
+ " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
+ " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
+ " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
+ " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
+ " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
+ " \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n"
+ " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "")
@@ -3038,6 +3051,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
obj.pushKV("hdseedid", seed_id.GetHex());
obj.pushKV("hdmasterkeyid", seed_id.GetHex());
}
+ obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
return obj;
}
@@ -3128,12 +3142,13 @@ static UniValue loadwallet(const JSONRPCRequest& request)
static UniValue createwallet(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() != 1) {
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error(
- "createwallet \"wallet_name\"\n"
+ "createwallet \"wallet_name\" ( disable_private_keys )\n"
"\nCreates and loads a new wallet.\n"
"\nArguments:\n"
- "1. \"wallet_name\" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.\n"
+ "1. \"wallet_name\" (string, required) The name for the new wallet. If this is a path, the wallet will be created at the path location.\n"
+ "2. disable_private_keys (boolean, optional, default: false) Disable the possibility of private keys (only watchonlys are possible in this mode).\n"
"\nResult:\n"
"{\n"
" \"name\" : <wallet_name>, (string) The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n"
@@ -3148,6 +3163,11 @@ static UniValue createwallet(const JSONRPCRequest& request)
std::string error;
std::string warning;
+ bool disable_privatekeys = false;
+ if (!request.params[1].isNull()) {
+ disable_privatekeys = request.params[1].get_bool();
+ }
+
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists.");
@@ -3158,7 +3178,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
}
- std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()));
+ std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
@@ -4756,7 +4776,7 @@ static const CRPCCommand commands[] =
{ "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index a946b565f1..c89b8f252f 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -365,4 +365,13 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
}
+BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
+{
+ std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>("dummy", WalletDatabase::CreateDummy());
+ wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ BOOST_CHECK(!wallet->TopUpKeyPool(1000));
+ CPubKey pubkey;
+ BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false));
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index da585a0603..aeed430111 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -164,6 +164,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
{
+ assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
@@ -1444,6 +1445,7 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
CPubKey CWallet::GenerateNewSeed()
{
+ assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
CKey key;
key.MakeNewKey(true);
return DeriveNewSeed(key);
@@ -1505,6 +1507,34 @@ bool CWallet::IsHDEnabled() const
return !hdChain.seed_id.IsNull();
}
+void CWallet::SetWalletFlag(uint64_t flags)
+{
+ LOCK(cs_wallet);
+ m_wallet_flags |= flags;
+ if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags))
+ throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
+}
+
+bool CWallet::IsWalletFlagSet(uint64_t flag)
+{
+ return (m_wallet_flags & flag);
+}
+
+bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
+{
+ LOCK(cs_wallet);
+ m_wallet_flags = overwriteFlags;
+ if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) {
+ // contains unknown non-tolerable wallet flags
+ return false;
+ }
+ if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
+ throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
+ }
+
+ return true;
+}
+
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
@@ -2720,6 +2750,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac
// post-backup change.
// Reserve a new key pair from key pool
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
+ return false;
+ }
CPubKey vchPubKey;
bool ret;
ret = reservekey.GetReservedKey(vchPubKey, true);
@@ -3120,7 +3154,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
LOCK(cs_KeyStore);
// This wallet is in its first run if all of these are empty
- fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty();
+ fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
}
if (nLoadWalletRet != DBErrors::LOAD_OK)
@@ -3244,6 +3278,9 @@ const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const
*/
bool CWallet::NewKeyPool()
{
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
{
LOCK(cs_wallet);
WalletBatch batch(*database);
@@ -3302,6 +3339,9 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
{
LOCK(cs_wallet);
@@ -3426,6 +3466,10 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
{
+ if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ return false;
+ }
+
CKeyPool keypool;
{
LOCK(cs_wallet);
@@ -3965,7 +4009,7 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string);
}
-std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
+std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags)
{
const std::string& walletFile = name;
@@ -4090,18 +4134,33 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
}
walletInstance->SetMinVersion(FEATURE_LATEST);
- // generate a new seed
- CPubKey seed = walletInstance->GenerateNewSeed();
- if (!walletInstance->SetHDSeed(seed))
- throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed");
+ if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ //selective allow to set flags
+ walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ } else {
+ // generate a new seed
+ CPubKey seed = walletInstance->GenerateNewSeed();
+ if (!walletInstance->SetHDSeed(seed)) {
+ throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed");
+ }
+ }
// Top up the keypool
- if (!walletInstance->TopUpKeyPool()) {
+ if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys") += "\n");
return nullptr;
}
walletInstance->ChainStateFlushed(chainActive.GetLocator());
+ } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
+ // Make it impossible to disable private keys after creation
+ InitError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile));
+ return NULL;
+ } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ LOCK(walletInstance->cs_KeyStore);
+ if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) {
+ InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile));
+ }
} else if (gArgs.IsArgSet("-usehd")) {
bool useHD = gArgs.GetBoolArg("-usehd", true);
if (walletInstance->IsHDEnabled() && !useHD) {
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 2e53ca0c55..85d7209a1d 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -100,6 +100,16 @@ constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT};
//! Default for -changetype
constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO};
+enum WalletFlags : uint64_t {
+ // wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
+ // unkown wallet flags in the lower section <= (1 << 31) will be tolerated
+
+ // will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
+ WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
+};
+
+static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS;
+
/** A key pool entry */
class CKeyPool
{
@@ -726,6 +736,7 @@ private:
std::set<int64_t> set_pre_split_keypool;
int64_t m_max_keypool_index = 0;
std::map<CKeyID, int64_t> m_pool_key_to_index;
+ std::atomic<uint64_t> m_wallet_flags{0};
int64_t nTimeFirstKey = 0;
@@ -1132,7 +1143,7 @@ public:
static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
- static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path);
+ static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0);
/**
* Wallet post-init setup
@@ -1185,6 +1196,16 @@ public:
/** Whether a given output is spendable by this wallet */
bool OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibility_filter) const;
+
+ /** set a single wallet flag */
+ void SetWalletFlag(uint64_t flags);
+
+ /** check if a certain wallet flag is set */
+ bool IsWalletFlagSet(uint64_t flag);
+
+ /** overwrite all flags by the given uint64_t
+ returns false if unknown, non-tolerable flags are present */
+ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
};
/** A key allocated from the key pool. */
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 9c25ee7d76..67fcaa725b 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -510,7 +510,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
strErr = "Error reading wallet database: SetHDChain failed";
return false;
}
- } else if (strType != "bestblock" && strType != "bestblock_nomerkle"){
+ } else if (strType == "flags") {
+ uint64_t flags;
+ ssValue >> flags;
+ if (!pwallet->SetWalletFlags(flags, true)) {
+ strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
+ return false;
+ }
+ } else if (strType != "bestblock" && strType != "bestblock_nomerkle") {
wss.m_unknown_records++;
}
} catch (...)
@@ -570,10 +577,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
{
// losing keys is considered a catastrophic error, anything else
// we assume the user can live with:
- if (IsKeyType(strType) || strType == "defaultkey")
+ if (IsKeyType(strType) || strType == "defaultkey") {
result = DBErrors::CORRUPT;
- else
- {
+ } else if(strType == "flags") {
+ // reading the wallet flags can only fail if unknown flags are present
+ result = DBErrors::TOO_NEW;
+ } else {
// Leave other errors alone, if we try to fix them we might make things worse.
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
if (strType == "tx")
@@ -840,6 +849,11 @@ bool WalletBatch::WriteHDChain(const CHDChain& chain)
return WriteIC(std::string("hdchain"), chain);
}
+bool WalletBatch::WriteWalletFlags(const uint64_t flags)
+{
+ return WriteIC(std::string("flags"), flags);
+}
+
bool WalletBatch::TxnBegin()
{
return m_batch.TxnBegin();
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 3237376f63..674d1c2201 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -234,6 +234,7 @@ public:
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
+ bool WriteWalletFlags(const uint64_t flags);
//! Begin a new transaction
bool TxnBegin();
//! Commit current transaction
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 287dc0e53e..50942aec40 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -351,8 +351,7 @@ class TestNodeCLI():
def send_cli(self, command=None, *args, **kwargs):
"""Run bitcoin-cli command. Deserializes returned string as python object."""
-
- pos_args = [str(arg) for arg in args]
+ pos_args = [str(arg).lower() if type(arg) is bool else str(arg) for arg in args]
named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
p_args = [self.binary, "-datadir=" + self.datadir] + self.options
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index d8a09c22e5..4c5acf69a9 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -98,6 +98,8 @@ BASE_SCRIPTS = [
'mempool_persist.py',
'wallet_multiwallet.py',
'wallet_multiwallet.py --usecli',
+ 'wallet_disableprivatekeys.py',
+ 'wallet_disableprivatekeys.py --usecli',
'interface_http.py',
'rpc_psbt.py',
'rpc_users.py',
diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py
new file mode 100755
index 0000000000..0ba2cfe9be
--- /dev/null
+++ b/test/functional/wallet_disableprivatekeys.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test disable-privatekeys mode.
+"""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import (
+ assert_raises_rpc_error,
+)
+
+
+class DisablePrivateKeysTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+ self.supports_cli = True
+
+ def run_test(self):
+ node = self.nodes[0]
+ self.log.info("Test disableprivatekeys creation.")
+ self.nodes[0].createwallet('w1', True)
+ self.nodes[0].createwallet('w2')
+ w1 = node.get_wallet_rpc('w1')
+ w2 = node.get_wallet_rpc('w2')
+ assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getnewaddress)
+ assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
+ w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey'])
+
+if __name__ == '__main__':
+ DisablePrivateKeysTest().main()