Skip to content

Commit 7687f78

Browse files
committed
[wallet] Support creating a blank wallet
A blank wallet is a wallet that has no keys, script or watch only things. A new wallet flag indicating that it is blank will be set when the wallet is blank. Once it is no longer blank (a seed has been generated, keys or scripts imported, etc), the flag will be unset.
1 parent 5c99bb0 commit 7687f78

File tree

13 files changed

+221
-71
lines changed

13 files changed

+221
-71
lines changed

doc/release-notes-15226.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Miscellaneous RPC changes
2+
------------
3+
4+
- The RPC `createwallet` now has an optional `blank` argument that can be used to create a blank wallet.
5+
Blank wallets do not have any keys or HD seed.
6+
They cannot be opened in software older than 0.18.
7+
Once a blank wallet has a HD seed set (by using `sethdseed`) or private keys, scripts, addresses, and other watch only things have been imported, the wallet is no longer blank and can be opened in 0.17.x.
8+
Encrypting a blank wallet will also set a HD seed for it.

src/interfaces/wallet.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ class WalletImpl : public Wallet
463463
}
464464
unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; }
465465
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
466+
bool canGetAddresses() override { return m_wallet.CanGetAddresses(); }
466467
bool IsWalletFlagSet(uint64_t flag) override { return m_wallet.IsWalletFlagSet(flag); }
467468
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
468469
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }

src/interfaces/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ class Wallet
235235
// Return whether HD enabled.
236236
virtual bool hdEnabled() = 0;
237237

238+
// Return whether the wallet is blank.
239+
virtual bool canGetAddresses() = 0;
240+
238241
// check if a certain wallet flag is set.
239242
virtual bool IsWalletFlagSet(uint64_t flag) = 0;
240243

src/qt/walletmodel.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -580,12 +580,7 @@ bool WalletModel::privateKeysDisabled() const
580580

581581
bool WalletModel::canGetAddresses() const
582582
{
583-
// The wallet can provide a fresh address if:
584-
// * hdEnabled(): an HD seed is present; or
585-
// * it is a legacy wallet, because:
586-
// * !hdEnabled(): an HD seed is not present; and
587-
// * !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS): private keys have not been disabled (which results in hdEnabled() == true)
588-
return m_wallet->hdEnabled() || (!m_wallet->hdEnabled() && !m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
583+
return m_wallet->canGetAddresses();
589584
}
590585

591586
QString WalletModel::getWalletName() const

src/rpc/client.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
161161
{ "rescanblockchain", 0, "start_height"},
162162
{ "rescanblockchain", 1, "stop_height"},
163163
{ "createwallet", 1, "disable_private_keys"},
164+
{ "createwallet", 2, "blank"},
164165
{ "getnodeaddresses", 0, "count"},
165166
{ "stop", 0, "wait" },
166167
};

src/wallet/crypter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no
182182
if (!SetCrypted())
183183
return false;
184184

185-
bool keyPass = false;
185+
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
186186
bool keyFail = false;
187187
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
188188
for (; mi != mapCryptedKeys.end(); ++mi)

src/wallet/rpcwallet.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,18 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
170170
},
171171
}.ToString());
172172

173+
// Belt and suspenders check for disabled private keys
173174
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
174175
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
175176
}
176177

177178
LOCK(pwallet->cs_wallet);
178179

180+
if (!pwallet->CanGetAddresses()) {
181+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
182+
}
183+
184+
179185
// Parse the label first so we don't generate a key if there's an error
180186
std::string label;
181187
if (!request.params[0].isNull())
@@ -231,12 +237,17 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
231237
},
232238
}.ToString());
233239

240+
// Belt and suspenders check for disabled private keys
234241
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
235242
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
236243
}
237244

238245
LOCK(pwallet->cs_wallet);
239246

247+
if (!pwallet->CanGetAddresses(true)) {
248+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
249+
}
250+
240251
if (!pwallet->IsLocked()) {
241252
pwallet->TopUpKeyPool();
242253
}
@@ -2578,13 +2589,14 @@ static UniValue loadwallet(const JSONRPCRequest& request)
25782589

25792590
static UniValue createwallet(const JSONRPCRequest& request)
25802591
{
2581-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
2592+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) {
25822593
throw std::runtime_error(
25832594
RPCHelpMan{"createwallet",
25842595
"\nCreates and loads a new wallet.\n",
25852596
{
25862597
{"wallet_name", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
25872598
{"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
2599+
{"blank", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
25882600
},
25892601
RPCResult{
25902602
"{\n"
@@ -2601,9 +2613,13 @@ static UniValue createwallet(const JSONRPCRequest& request)
26012613
std::string error;
26022614
std::string warning;
26032615

2604-
bool disable_privatekeys = false;
2605-
if (!request.params[1].isNull()) {
2606-
disable_privatekeys = request.params[1].get_bool();
2616+
uint64_t flags = 0;
2617+
if (!request.params[1].isNull() && request.params[1].get_bool()) {
2618+
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
2619+
}
2620+
2621+
if (!request.params[2].isNull() && request.params[2].get_bool()) {
2622+
flags |= WALLET_FLAG_BLANK_WALLET;
26072623
}
26082624

26092625
WalletLocation location(request.params[0].get_str());
@@ -2616,7 +2632,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
26162632
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
26172633
}
26182634

2619-
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
2635+
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, flags);
26202636
if (!wallet) {
26212637
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
26222638
}
@@ -3880,7 +3896,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
38803896
LOCK(pwallet->cs_wallet);
38813897

38823898
// Do not do anything to non-HD wallets
3883-
if (!pwallet->IsHDEnabled()) {
3899+
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
38843900
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with -upgradewallet in order to upgrade a non-HD wallet to HD");
38853901
}
38863902

@@ -4184,7 +4200,7 @@ static const CRPCCommand commands[] =
41844200
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
41854201
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
41864202
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
4187-
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} },
4203+
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
41884204
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
41894205
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
41904206
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },

src/wallet/test/wallet_tests.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
449449
{
450450
auto chain = interfaces::MakeChain();
451451
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy());
452+
wallet->SetMinVersion(FEATURE_LATEST);
452453
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
453454
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
454455
CPubKey pubkey;

src/wallet/wallet.cpp

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
168168
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
169169
{
170170
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
171+
assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
171172
AssertLockHeld(cs_wallet); // mapKeyMetadata
172173
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
173174

@@ -177,7 +178,7 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
177178
int64_t nCreationTime = GetTime();
178179
CKeyMetadata metadata(nCreationTime);
179180

180-
// use HD key derivation if HD was enabled during wallet creation
181+
// use HD key derivation if HD was enabled during wallet creation and a seed is present
181182
if (IsHDEnabled()) {
182183
DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
183184
} else {
@@ -283,6 +284,7 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C
283284
secret.GetPrivKey(),
284285
mapKeyMetadata[pubkey.GetID()]);
285286
}
287+
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
286288
return true;
287289
}
288290

@@ -349,7 +351,11 @@ bool CWallet::AddCScript(const CScript& redeemScript)
349351
{
350352
if (!CCryptoKeyStore::AddCScript(redeemScript))
351353
return false;
352-
return WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript);
354+
if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript)) {
355+
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
356+
return true;
357+
}
358+
return false;
353359
}
354360

355361
bool CWallet::LoadCScript(const CScript& redeemScript)
@@ -374,7 +380,11 @@ bool CWallet::AddWatchOnly(const CScript& dest)
374380
const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
375381
UpdateTimeFirstKey(meta.nCreateTime);
376382
NotifyWatchonlyChanged(true);
377-
return WalletBatch(*database).WriteWatchOnly(dest, meta);
383+
if (WalletBatch(*database).WriteWatchOnly(dest, meta)) {
384+
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
385+
return true;
386+
}
387+
return false;
378388
}
379389

380390
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
@@ -1402,6 +1412,7 @@ void CWallet::SetHDSeed(const CPubKey& seed)
14021412
newHdChain.seed_id = seed.GetID();
14031413
SetHDChain(newHdChain, false);
14041414
NotifyCanGetAddressesChanged();
1415+
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
14051416
}
14061417

14071418
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
@@ -1418,6 +1429,30 @@ bool CWallet::IsHDEnabled() const
14181429
return !hdChain.seed_id.IsNull();
14191430
}
14201431

1432+
bool CWallet::CanGenerateKeys()
1433+
{
1434+
// A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD)
1435+
LOCK(cs_wallet);
1436+
return IsHDEnabled() || !CanSupportFeature(FEATURE_HD);
1437+
}
1438+
1439+
bool CWallet::CanGetAddresses(bool internal)
1440+
{
1441+
LOCK(cs_wallet);
1442+
// Check if the keypool has keys
1443+
bool keypool_has_keys;
1444+
if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) {
1445+
keypool_has_keys = setInternalKeyPool.size() > 0;
1446+
} else {
1447+
keypool_has_keys = KeypoolCountExternalKeys() > 0;
1448+
}
1449+
// If the keypool doesn't have keys, check if we can generate them
1450+
if (!keypool_has_keys) {
1451+
return CanGenerateKeys();
1452+
}
1453+
return keypool_has_keys;
1454+
}
1455+
14211456
void CWallet::SetWalletFlag(uint64_t flags)
14221457
{
14231458
LOCK(cs_wallet);
@@ -1426,6 +1461,14 @@ void CWallet::SetWalletFlag(uint64_t flags)
14261461
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
14271462
}
14281463

1464+
void CWallet::UnsetWalletFlag(uint64_t flag)
1465+
{
1466+
LOCK(cs_wallet);
1467+
m_wallet_flags &= ~flag;
1468+
if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags))
1469+
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
1470+
}
1471+
14291472
bool CWallet::IsWalletFlagSet(uint64_t flag)
14301473
{
14311474
return (m_wallet_flags & flag);
@@ -3101,7 +3144,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
31013144
{
31023145
LOCK(cs_KeyStore);
31033146
// This wallet is in its first run if all of these are empty
3104-
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
3147+
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty()
3148+
&& !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
31053149
}
31063150

31073151
if (nLoadWalletRet != DBErrors::LOAD_OK)
@@ -3286,7 +3330,7 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
32863330

32873331
bool CWallet::TopUpKeyPool(unsigned int kpSize)
32883332
{
3289-
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3333+
if (!CanGenerateKeys()) {
32903334
return false;
32913335
}
32923336
{
@@ -3416,7 +3460,7 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
34163460

34173461
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
34183462
{
3419-
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3463+
if (!CanGetAddresses(internal)) {
34203464
return false;
34213465
}
34223466

@@ -3617,6 +3661,10 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
36173661

36183662
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
36193663
{
3664+
if (!pwallet->CanGetAddresses(internal)) {
3665+
return false;
3666+
}
3667+
36203668
if (nIndex == -1)
36213669
{
36223670
CKeyPool keypool;
@@ -4071,14 +4119,16 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
40714119
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
40724120
//selective allow to set flags
40734121
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
4122+
} else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) {
4123+
walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET);
40744124
} else {
40754125
// generate a new seed
40764126
CPubKey seed = walletInstance->GenerateNewSeed();
40774127
walletInstance->SetHDSeed(seed);
4078-
}
4128+
} // Otherwise, do not generate a new seed
40794129

40804130
// Top up the keypool
4081-
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
4131+
if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) {
40824132
InitError(_("Unable to generate initial keys"));
40834133
return nullptr;
40844134
}

src/wallet/wallet.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,21 @@ enum WalletFlags : uint64_t {
136136

137137
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
138138
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
139+
140+
//! Flag set when a wallet contains no HD seed and no private keys, scripts,
141+
//! addresses, and other watch only things, and is therefore "blank."
142+
//!
143+
//! The only function this flag serves is to distinguish a blank wallet from
144+
//! a newly created wallet when the wallet database is loaded, to avoid
145+
//! initialization that should only happen on first run.
146+
//!
147+
//! This flag is also a mandatory flag to prevent previous versions of
148+
//! bitcoin from opening the wallet, thinking it was newly created, and
149+
//! then improperly reinitializing it.
150+
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
139151
};
140152

141-
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS;
153+
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET;
142154

143155
/** A key pool entry */
144156
class CKeyPool
@@ -1132,6 +1144,12 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
11321144
/* Returns true if HD is enabled */
11331145
bool IsHDEnabled() const;
11341146

1147+
/* Returns true if the wallet can generate new keys */
1148+
bool CanGenerateKeys();
1149+
1150+
/* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
1151+
bool CanGetAddresses(bool internal = false);
1152+
11351153
/* Generates a new HD seed (will not be activated) */
11361154
CPubKey GenerateNewSeed();
11371155

@@ -1169,6 +1187,9 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
11691187
/** set a single wallet flag */
11701188
void SetWalletFlag(uint64_t flags);
11711189

1190+
/** Unsets a single wallet flag */
1191+
void UnsetWalletFlag(uint64_t flag);
1192+
11721193
/** check if a certain wallet flag is set */
11731194
bool IsWalletFlagSet(uint64_t flag);
11741195

0 commit comments

Comments
 (0)