Merge #9662: Add createwallet "disableprivatekeys" option: a sane mode for watchonly-wallets
a3fa4d6a6aQA: Fix bug in -usecli logic that converts booleans to non-lowercase strings (Jonas Schnelli)4704e5f074[QA] add createwallet disableprivatekey test (Jonas Schnelli)c7b8f343e9[Qt] Disable creating receive addresses when private keys are disabled (Jonas Schnelli)2f15c2bc20Add disable privatekeys option to createwallet (Jonas Schnelli)cebefba085Add option to disable private keys during internal wallet creation (Jonas Schnelli)9995a602a6Add 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
This commit is contained in:
+66
-7
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user