Skip to content

Commit

Permalink
[wallet] Support creating a blank wallet
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
achow101 committed Feb 10, 2019
1 parent 5c99bb0 commit 7687f78
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 71 deletions.
8 changes: 8 additions & 0 deletions doc/release-notes-15226.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Miscellaneous RPC changes
------------

- The RPC `createwallet` now has an optional `blank` argument that can be used to create a blank wallet.
Blank wallets do not have any keys or HD seed.
They cannot be opened in software older than 0.18.
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.
Encrypting a blank wallet will also set a HD seed for it.
1 change: 1 addition & 0 deletions src/interfaces/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ class WalletImpl : public Wallet
}
unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; }
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
bool canGetAddresses() override { return m_wallet.CanGetAddresses(); }
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; }
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ class Wallet
// Return whether HD enabled.
virtual bool hdEnabled() = 0;

// Return whether the wallet is blank.
virtual bool canGetAddresses() = 0;

// check if a certain wallet flag is set.
virtual bool IsWalletFlagSet(uint64_t flag) = 0;

Expand Down
7 changes: 1 addition & 6 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -580,12 +580,7 @@ bool WalletModel::privateKeysDisabled() const

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

QString WalletModel::getWalletName() const
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rescanblockchain", 0, "start_height"},
{ "rescanblockchain", 1, "stop_height"},
{ "createwallet", 1, "disable_private_keys"},
{ "createwallet", 2, "blank"},
{ "getnodeaddresses", 0, "count"},
{ "stop", 0, "wait" },
};
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/crypter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no
if (!SetCrypted())
return false;

bool keyPass = false;
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi)
Expand Down
30 changes: 23 additions & 7 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,18 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
},
}.ToString());

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

LOCK(pwallet->cs_wallet);

if (!pwallet->CanGetAddresses()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
}


// Parse the label first so we don't generate a key if there's an error
std::string label;
if (!request.params[0].isNull())
Expand Down Expand Up @@ -231,12 +237,17 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
},
}.ToString());

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

LOCK(pwallet->cs_wallet);

if (!pwallet->CanGetAddresses(true)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
}

if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
Expand Down Expand Up @@ -2578,13 +2589,14 @@ static UniValue loadwallet(const JSONRPCRequest& request)

static UniValue createwallet(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) {
throw std::runtime_error(
RPCHelpMan{"createwallet",
"\nCreates and loads a new wallet.\n",
{
{"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."},
{"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
{"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."},
},
RPCResult{
"{\n"
Expand All @@ -2601,9 +2613,13 @@ 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();
uint64_t flags = 0;
if (!request.params[1].isNull() && request.params[1].get_bool()) {
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
}

if (!request.params[2].isNull() && request.params[2].get_bool()) {
flags |= WALLET_FLAG_BLANK_WALLET;
}

WalletLocation location(request.params[0].get_str());
Expand All @@ -2616,7 +2632,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(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, flags);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
Expand Down Expand Up @@ -3880,7 +3896,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
LOCK(pwallet->cs_wallet);

// Do not do anything to non-HD wallets
if (!pwallet->IsHDEnabled()) {
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
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");
}

Expand Down Expand Up @@ -4184,7 +4200,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} },
{ "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
Expand Down
1 change: 1 addition & 0 deletions src/wallet/test/wallet_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
auto chain = interfaces::MakeChain();
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy());
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
CPubKey pubkey;
Expand Down
66 changes: 58 additions & 8 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
{
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets

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

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

Expand Down Expand Up @@ -349,7 +351,11 @@ bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
return WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript);
if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), redeemScript)) {
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
return true;
}
return false;
}

bool CWallet::LoadCScript(const CScript& redeemScript)
Expand All @@ -374,7 +380,11 @@ bool CWallet::AddWatchOnly(const CScript& dest)
const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
return WalletBatch(*database).WriteWatchOnly(dest, meta);
if (WalletBatch(*database).WriteWatchOnly(dest, meta)) {
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
return true;
}
return false;
}

bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
Expand Down Expand Up @@ -1402,6 +1412,7 @@ void CWallet::SetHDSeed(const CPubKey& seed)
newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
NotifyCanGetAddressesChanged();
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
}

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

bool CWallet::CanGenerateKeys()
{
// A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a non-HD wallet (pre FEATURE_HD)
LOCK(cs_wallet);
return IsHDEnabled() || !CanSupportFeature(FEATURE_HD);
}

bool CWallet::CanGetAddresses(bool internal)
{
LOCK(cs_wallet);
// Check if the keypool has keys
bool keypool_has_keys;
if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) {
keypool_has_keys = setInternalKeyPool.size() > 0;
} else {
keypool_has_keys = KeypoolCountExternalKeys() > 0;
}
// If the keypool doesn't have keys, check if we can generate them
if (!keypool_has_keys) {
return CanGenerateKeys();
}
return keypool_has_keys;
}

void CWallet::SetWalletFlag(uint64_t flags)
{
LOCK(cs_wallet);
Expand All @@ -1426,6 +1461,14 @@ void CWallet::SetWalletFlag(uint64_t flags)
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}

void CWallet::UnsetWalletFlag(uint64_t flag)
{
LOCK(cs_wallet);
m_wallet_flags &= ~flag;
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);
Expand Down Expand Up @@ -3101,7 +3144,8 @@ 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() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty()
&& !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
}

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

bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (!CanGenerateKeys()) {
return false;
}
{
Expand Down Expand Up @@ -3416,7 +3460,7 @@ 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)) {
if (!CanGetAddresses(internal)) {
return false;
}

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

bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
{
if (!pwallet->CanGetAddresses(internal)) {
return false;
}

if (nIndex == -1)
{
CKeyPool keypool;
Expand Down Expand Up @@ -4071,14 +4119,16 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
//selective allow to set flags
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
} else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) {
walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET);
} else {
// generate a new seed
CPubKey seed = walletInstance->GenerateNewSeed();
walletInstance->SetHDSeed(seed);
}
} // Otherwise, do not generate a new seed

// Top up the keypool
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys"));
return nullptr;
}
Expand Down
23 changes: 22 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,21 @@ enum WalletFlags : uint64_t {

// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),

//! Flag set when a wallet contains no HD seed and no private keys, scripts,
//! addresses, and other watch only things, and is therefore "blank."
//!
//! The only function this flag serves is to distinguish a blank wallet from
//! a newly created wallet when the wallet database is loaded, to avoid
//! initialization that should only happen on first run.
//!
//! This flag is also a mandatory flag to prevent previous versions of
//! bitcoin from opening the wallet, thinking it was newly created, and
//! then improperly reinitializing it.
WALLET_FLAG_BLANK_WALLET = (1ULL << 33),
};

static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS;
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET;

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

/* Returns true if the wallet can generate new keys */
bool CanGenerateKeys();

/* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
bool CanGetAddresses(bool internal = false);

/* Generates a new HD seed (will not be activated) */
CPubKey GenerateNewSeed();

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

/** Unsets a single wallet flag */
void UnsetWalletFlag(uint64_t flag);

/** check if a certain wallet flag is set */
bool IsWalletFlagSet(uint64_t flag);

Expand Down
4 changes: 2 additions & 2 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@
'mempool_persist.py',
'wallet_multiwallet.py',
'wallet_multiwallet.py --usecli',
'wallet_disableprivatekeys.py',
'wallet_disableprivatekeys.py --usecli',
'wallet_createwallet.py',
'wallet_createwallet.py --usecli',
'interface_http.py',
'interface_rpc.py',
'rpc_psbt.py',
Expand Down
Loading

0 comments on commit 7687f78

Please sign in to comment.