Skip to content
Permalink
Browse files

[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.
  • Loading branch information...
achow101 committed Feb 7, 2019
1 parent 5c99bb0 commit 8061ea4611696b22d5d5dcdd73c1e053ffecc09d
@@ -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 private 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.
@@ -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 isBlank() override { return m_wallet.IsBlank(); }
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; }
@@ -235,6 +235,9 @@ class Wallet
// Return whether HD enabled.
virtual bool hdEnabled() = 0;

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

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

@@ -581,11 +581,12 @@ bool WalletModel::privateKeysDisabled() const
bool WalletModel::canGetAddresses() const
{
// The wallet can provide a fresh address if:
// * hdEnabled(): an HD seed is present; or
// * hdEnabled(): a 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));
// * !hdEnabled(): a HD seed is not present; and
// * !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS): private keys have not been disabled
// * !isBlank(): the wallet is not blank
return m_wallet->hdEnabled() || (!m_wallet->hdEnabled() && !m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !m_wallet->isBlank());
}

QString WalletModel::getWalletName() const
@@ -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" },
};
@@ -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)
@@ -173,6 +173,9 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
if (pwallet->IsBlank()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet is blank and has no keys");
}

LOCK(pwallet->cs_wallet);

@@ -234,6 +237,9 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
if (pwallet->IsBlank()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet is blank and has no keys");
}

LOCK(pwallet->cs_wallet);

@@ -2578,13 +2584,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"
@@ -2601,9 +2608,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());
@@ -2616,7 +2627,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.");
}
@@ -3880,7 +3891,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
LOCK(pwallet->cs_wallet);

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

@@ -4184,7 +4195,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"} },
@@ -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

@@ -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 {
@@ -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;
}

@@ -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)
@@ -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)
@@ -703,7 +713,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
Lock();
Unlock(strWalletPassphrase);

// if we are using HD, replace the HD seed with a new one
// if we are using HD, create a fresh seed or replace the existing seed with a new one
if (IsHDEnabled()) {
SetHDSeed(GenerateNewSeed());
}
@@ -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)
@@ -1418,6 +1429,13 @@ bool CWallet::IsHDEnabled() const
return !hdChain.seed_id.IsNull();
}

bool CWallet::IsBlank()
{
// A Blank wallet either has the flag set or it has a version number requiring HD but no HD seed is set
LOCK(cs_wallet);
return IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET) || (CanSupportFeature(FEATURE_HD) && !IsHDEnabled());
}

void CWallet::SetWalletFlag(uint64_t flags)
{
LOCK(cs_wallet);
@@ -1426,6 +1444,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);
@@ -3101,7 +3127,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)
@@ -3295,6 +3322,9 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
if (IsLocked())
return false;

// If wallet is blank, don't top up the keypool but don't fail TopUpKeyPool()
if (IsBlank()) return true;

// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0)
@@ -3416,7 +3446,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 (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) || IsBlank()) {
return false;
}

@@ -4071,14 +4101,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->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET) && !walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys"));
return nullptr;
}
@@ -136,9 +136,12 @@ 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),

// Will enforce that the wallet should be blank
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
@@ -1132,6 +1135,9 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
/* Returns true if HD is enabled */
bool IsHDEnabled() const;

/* Returns true if the wallet is blank */
bool IsBlank();

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

@@ -1169,6 +1175,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);

@@ -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',

0 comments on commit 8061ea4

Please sign in to comment.
You can’t perform that action at this time.