Merge #15226: Allow creating blank (empty) wallets (alternative)
7687f7873
[wallet] Support creating a blank wallet (Andrew Chow)
Pull request description:
Alternative (kind of) to #14938
This PR adds a `blank` parameter to the `createwallet` RPC to create a wallet that has no private keys initially. `sethdseed` can then be used to make a clean wallet with a custom seed. `encryptwallet` can also be used to make a wallet that is born encrypted.
Instead of changing the version number as done in #14938, a wallet flag is used to indicate that the wallet should be blank. This flag is set at creation, and then unset when the wallet is no longer blank. A wallet becomes non-blank when a HD seed is set or anything is imported. The main change to create a blank wallet is primarily taken from #14938.
Also with this, the term "blank wallet" is used instead of "empty wallet" to avoid confusion with wallets that have balance which would also be referred to as "empty".
This is built on top of #15225 in order to fix GUI issues.
Tree-SHA512: 824d685e11ac2259a26b5ece99c67a7bda94a570cd921472c464243ee356b7734595ad35cc439b34357135df041ed9cba951e6edac194935c3a55a1dc4fcbdea
This commit is contained in:
commit
6f4e0d1542
13 changed files with 221 additions and 71 deletions
8
doc/release-notes-15226.md
Normal file
8
doc/release-notes-15226.md
Normal file
|
@ -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.
|
|
@ -464,6 +464,7 @@ public:
|
||||||
}
|
}
|
||||||
unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; }
|
unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; }
|
||||||
bool hdEnabled() override { return m_wallet.IsHDEnabled(); }
|
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); }
|
bool IsWalletFlagSet(uint64_t flag) override { return m_wallet.IsWalletFlagSet(flag); }
|
||||||
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
|
OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; }
|
||||||
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }
|
OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; }
|
||||||
|
|
|
@ -235,6 +235,9 @@ public:
|
||||||
// Return whether HD enabled.
|
// Return whether HD enabled.
|
||||||
virtual bool hdEnabled() = 0;
|
virtual bool hdEnabled() = 0;
|
||||||
|
|
||||||
|
// Return whether the wallet is blank.
|
||||||
|
virtual bool canGetAddresses() = 0;
|
||||||
|
|
||||||
// check if a certain wallet flag is set.
|
// check if a certain wallet flag is set.
|
||||||
virtual bool IsWalletFlagSet(uint64_t flag) = 0;
|
virtual bool IsWalletFlagSet(uint64_t flag) = 0;
|
||||||
|
|
||||||
|
|
|
@ -580,12 +580,7 @@ bool WalletModel::privateKeysDisabled() const
|
||||||
|
|
||||||
bool WalletModel::canGetAddresses() const
|
bool WalletModel::canGetAddresses() const
|
||||||
{
|
{
|
||||||
// The wallet can provide a fresh address if:
|
return m_wallet->canGetAddresses();
|
||||||
// * 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WalletModel::getWalletName() const
|
QString WalletModel::getWalletName() const
|
||||||
|
|
|
@ -161,6 +161,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "rescanblockchain", 0, "start_height"},
|
{ "rescanblockchain", 0, "start_height"},
|
||||||
{ "rescanblockchain", 1, "stop_height"},
|
{ "rescanblockchain", 1, "stop_height"},
|
||||||
{ "createwallet", 1, "disable_private_keys"},
|
{ "createwallet", 1, "disable_private_keys"},
|
||||||
|
{ "createwallet", 2, "blank"},
|
||||||
{ "getnodeaddresses", 0, "count"},
|
{ "getnodeaddresses", 0, "count"},
|
||||||
{ "stop", 0, "wait" },
|
{ "stop", 0, "wait" },
|
||||||
};
|
};
|
||||||
|
|
|
@ -182,7 +182,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no
|
||||||
if (!SetCrypted())
|
if (!SetCrypted())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool keyPass = false;
|
bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
|
||||||
bool keyFail = false;
|
bool keyFail = false;
|
||||||
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
|
||||||
for (; mi != mapCryptedKeys.end(); ++mi)
|
for (; mi != mapCryptedKeys.end(); ++mi)
|
||||||
|
|
|
@ -170,12 +170,18 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
|
||||||
},
|
},
|
||||||
}.ToString());
|
}.ToString());
|
||||||
|
|
||||||
|
// Belt and suspenders check for disabled private keys
|
||||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK(pwallet->cs_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
|
// Parse the label first so we don't generate a key if there's an error
|
||||||
std::string label;
|
std::string label;
|
||||||
if (!request.params[0].isNull())
|
if (!request.params[0].isNull())
|
||||||
|
@ -231,12 +237,17 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
|
||||||
},
|
},
|
||||||
}.ToString());
|
}.ToString());
|
||||||
|
|
||||||
|
// Belt and suspenders check for disabled private keys
|
||||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
|
if (!pwallet->CanGetAddresses(true)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
|
||||||
|
}
|
||||||
|
|
||||||
if (!pwallet->IsLocked()) {
|
if (!pwallet->IsLocked()) {
|
||||||
pwallet->TopUpKeyPool();
|
pwallet->TopUpKeyPool();
|
||||||
}
|
}
|
||||||
|
@ -2578,13 +2589,14 @@ static UniValue loadwallet(const JSONRPCRequest& request)
|
||||||
|
|
||||||
static UniValue createwallet(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(
|
throw std::runtime_error(
|
||||||
RPCHelpMan{"createwallet",
|
RPCHelpMan{"createwallet",
|
||||||
"\nCreates and loads a new wallet.\n",
|
"\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."},
|
{"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)."},
|
{"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{
|
RPCResult{
|
||||||
"{\n"
|
"{\n"
|
||||||
|
@ -2601,9 +2613,13 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||||
std::string error;
|
std::string error;
|
||||||
std::string warning;
|
std::string warning;
|
||||||
|
|
||||||
bool disable_privatekeys = false;
|
uint64_t flags = 0;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull() && request.params[1].get_bool()) {
|
||||||
disable_privatekeys = 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());
|
WalletLocation location(request.params[0].get_str());
|
||||||
|
@ -2616,7 +2632,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
|
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) {
|
if (!wallet) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
|
||||||
}
|
}
|
||||||
|
@ -3886,7 +3902,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
// Do not do anything to non-HD wallets
|
// 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");
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4190,7 +4206,7 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
|
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
|
||||||
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
|
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
|
||||||
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
|
{ "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", "dumpprivkey", &dumpprivkey, {"address"} },
|
||||||
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
|
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
|
||||||
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
|
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
|
||||||
|
|
|
@ -452,6 +452,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
|
||||||
{
|
{
|
||||||
auto chain = interfaces::MakeChain();
|
auto chain = interfaces::MakeChain();
|
||||||
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy());
|
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, WalletLocation(), WalletDatabase::CreateDummy());
|
||||||
|
wallet->SetMinVersion(FEATURE_LATEST);
|
||||||
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||||
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
|
||||||
CPubKey pubkey;
|
CPubKey pubkey;
|
||||||
|
|
|
@ -168,6 +168,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
||||||
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
|
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
|
||||||
{
|
{
|
||||||
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
||||||
|
assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
|
||||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||||
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
|
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();
|
int64_t nCreationTime = GetTime();
|
||||||
CKeyMetadata metadata(nCreationTime);
|
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()) {
|
if (IsHDEnabled()) {
|
||||||
DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
|
DeriveNewChildKey(batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
|
||||||
} else {
|
} else {
|
||||||
|
@ -283,6 +284,7 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C
|
||||||
secret.GetPrivKey(),
|
secret.GetPrivKey(),
|
||||||
mapKeyMetadata[pubkey.GetID()]);
|
mapKeyMetadata[pubkey.GetID()]);
|
||||||
}
|
}
|
||||||
|
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +351,11 @@ bool CWallet::AddCScript(const CScript& redeemScript)
|
||||||
{
|
{
|
||||||
if (!CCryptoKeyStore::AddCScript(redeemScript))
|
if (!CCryptoKeyStore::AddCScript(redeemScript))
|
||||||
return false;
|
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)
|
bool CWallet::LoadCScript(const CScript& redeemScript)
|
||||||
|
@ -374,7 +380,11 @@ bool CWallet::AddWatchOnly(const CScript& dest)
|
||||||
const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
|
const CKeyMetadata& meta = m_script_metadata[CScriptID(dest)];
|
||||||
UpdateTimeFirstKey(meta.nCreateTime);
|
UpdateTimeFirstKey(meta.nCreateTime);
|
||||||
NotifyWatchonlyChanged(true);
|
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)
|
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
|
||||||
|
@ -1402,6 +1412,7 @@ void CWallet::SetHDSeed(const CPubKey& seed)
|
||||||
newHdChain.seed_id = seed.GetID();
|
newHdChain.seed_id = seed.GetID();
|
||||||
SetHDChain(newHdChain, false);
|
SetHDChain(newHdChain, false);
|
||||||
NotifyCanGetAddressesChanged();
|
NotifyCanGetAddressesChanged();
|
||||||
|
UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
|
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
|
||||||
|
@ -1418,6 +1429,30 @@ bool CWallet::IsHDEnabled() const
|
||||||
return !hdChain.seed_id.IsNull();
|
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)
|
void CWallet::SetWalletFlag(uint64_t flags)
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
@ -1426,6 +1461,14 @@ void CWallet::SetWalletFlag(uint64_t flags)
|
||||||
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
|
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)
|
bool CWallet::IsWalletFlagSet(uint64_t flag)
|
||||||
{
|
{
|
||||||
return (m_wallet_flags & flag);
|
return (m_wallet_flags & flag);
|
||||||
|
@ -3103,7 +3146,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
||||||
{
|
{
|
||||||
LOCK(cs_KeyStore);
|
LOCK(cs_KeyStore);
|
||||||
// This wallet is in its first run if all of these are empty
|
// 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)
|
if (nLoadWalletRet != DBErrors::LOAD_OK)
|
||||||
|
@ -3288,7 +3332,7 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
|
||||||
|
|
||||||
bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||||
{
|
{
|
||||||
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if (!CanGenerateKeys()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -3418,7 +3462,7 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
|
||||||
|
|
||||||
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
|
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
|
||||||
{
|
{
|
||||||
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if (!CanGetAddresses(internal)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3619,6 +3663,10 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
|
||||||
|
|
||||||
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
|
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
|
||||||
{
|
{
|
||||||
|
if (!pwallet->CanGetAddresses(internal)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (nIndex == -1)
|
if (nIndex == -1)
|
||||||
{
|
{
|
||||||
CKeyPool keypool;
|
CKeyPool keypool;
|
||||||
|
@ -4072,14 +4120,16 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
|
||||||
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||||
//selective allow to set flags
|
//selective allow to set flags
|
||||||
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
|
||||||
|
} else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) {
|
||||||
|
walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET);
|
||||||
} else {
|
} else {
|
||||||
// generate a new seed
|
// generate a new seed
|
||||||
CPubKey seed = walletInstance->GenerateNewSeed();
|
CPubKey seed = walletInstance->GenerateNewSeed();
|
||||||
walletInstance->SetHDSeed(seed);
|
walletInstance->SetHDSeed(seed);
|
||||||
}
|
} // Otherwise, do not generate a new seed
|
||||||
|
|
||||||
// Top up the keypool
|
// 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"));
|
InitError(_("Unable to generate initial keys"));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
|
||||||
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
|
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 */
|
/** A key pool entry */
|
||||||
class CKeyPool
|
class CKeyPool
|
||||||
|
@ -1132,6 +1144,12 @@ public:
|
||||||
/* Returns true if HD is enabled */
|
/* Returns true if HD is enabled */
|
||||||
bool IsHDEnabled() const;
|
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) */
|
/* Generates a new HD seed (will not be activated) */
|
||||||
CPubKey GenerateNewSeed();
|
CPubKey GenerateNewSeed();
|
||||||
|
|
||||||
|
@ -1169,6 +1187,9 @@ public:
|
||||||
/** set a single wallet flag */
|
/** set a single wallet flag */
|
||||||
void SetWalletFlag(uint64_t flags);
|
void SetWalletFlag(uint64_t flags);
|
||||||
|
|
||||||
|
/** Unsets a single wallet flag */
|
||||||
|
void UnsetWalletFlag(uint64_t flag);
|
||||||
|
|
||||||
/** check if a certain wallet flag is set */
|
/** check if a certain wallet flag is set */
|
||||||
bool IsWalletFlagSet(uint64_t flag);
|
bool IsWalletFlagSet(uint64_t flag);
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,8 @@ BASE_SCRIPTS = [
|
||||||
'mempool_persist.py',
|
'mempool_persist.py',
|
||||||
'wallet_multiwallet.py',
|
'wallet_multiwallet.py',
|
||||||
'wallet_multiwallet.py --usecli',
|
'wallet_multiwallet.py --usecli',
|
||||||
'wallet_disableprivatekeys.py',
|
'wallet_createwallet.py',
|
||||||
'wallet_disableprivatekeys.py --usecli',
|
'wallet_createwallet.py --usecli',
|
||||||
'interface_http.py',
|
'interface_http.py',
|
||||||
'interface_rpc.py',
|
'interface_rpc.py',
|
||||||
'rpc_psbt.py',
|
'rpc_psbt.py',
|
||||||
|
|
100
test/functional/wallet_createwallet.py
Executable file
100
test/functional/wallet_createwallet.py
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
#!/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 createwallet arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
assert_equal,
|
||||||
|
assert_raises_rpc_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
class CreateWalletTest(BitcoinTestFramework):
|
||||||
|
def set_test_params(self):
|
||||||
|
self.setup_clean_chain = False
|
||||||
|
self.num_nodes = 1
|
||||||
|
self.supports_cli = True
|
||||||
|
|
||||||
|
def skip_test_if_missing_module(self):
|
||||||
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
node = self.nodes[0]
|
||||||
|
node.generate(1) # Leave IBD for sethdseed
|
||||||
|
|
||||||
|
self.nodes[0].createwallet(wallet_name='w0')
|
||||||
|
w0 = node.get_wallet_rpc('w0')
|
||||||
|
address1 = w0.getnewaddress()
|
||||||
|
|
||||||
|
self.log.info("Test disableprivatekeys creation.")
|
||||||
|
self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
|
||||||
|
w1 = node.get_wallet_rpc('w1')
|
||||||
|
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(w0.getaddressinfo(address1)['pubkey'])
|
||||||
|
|
||||||
|
self.log.info('Test that private keys cannot be imported')
|
||||||
|
addr = w0.getnewaddress('', 'legacy')
|
||||||
|
privkey = w0.dumpprivkey(addr)
|
||||||
|
assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
|
||||||
|
result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
|
||||||
|
assert(not result[0]['success'])
|
||||||
|
assert('warning' not in result[0])
|
||||||
|
assert_equal(result[0]['error']['code'], -4)
|
||||||
|
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
|
||||||
|
|
||||||
|
self.log.info("Test blank creation with private keys disabled.")
|
||||||
|
self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
|
||||||
|
w2 = node.get_wallet_rpc('w2')
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getrawchangeaddress)
|
||||||
|
w2.importpubkey(w0.getaddressinfo(address1)['pubkey'])
|
||||||
|
|
||||||
|
self.log.info("Test blank creation with private keys enabled.")
|
||||||
|
self.nodes[0].createwallet(wallet_name='w3', disable_private_keys=False, blank=True)
|
||||||
|
w3 = node.get_wallet_rpc('w3')
|
||||||
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress)
|
||||||
|
# Import private key
|
||||||
|
w3.importprivkey(w0.dumpprivkey(address1))
|
||||||
|
# Imported private keys are currently ignored by the keypool
|
||||||
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
|
||||||
|
# Set the seed
|
||||||
|
w3.sethdseed()
|
||||||
|
assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
|
||||||
|
w3.getnewaddress()
|
||||||
|
w3.getrawchangeaddress()
|
||||||
|
|
||||||
|
self.log.info("Test blank creation with privkeys enabled and then encryption")
|
||||||
|
self.nodes[0].createwallet(wallet_name='w4', disable_private_keys=False, blank=True)
|
||||||
|
w4 = node.get_wallet_rpc('w4')
|
||||||
|
assert_equal(w4.getwalletinfo()['keypoolsize'], 0)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
|
||||||
|
# Encrypt the wallet. Nothing should change about the keypool
|
||||||
|
w4.encryptwallet('pass')
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
|
||||||
|
# Now set a seed and it should work. Wallet should also be encrypted
|
||||||
|
w4.walletpassphrase('pass', 2)
|
||||||
|
w4.sethdseed()
|
||||||
|
w4.getnewaddress()
|
||||||
|
w4.getrawchangeaddress()
|
||||||
|
|
||||||
|
self.log.info("Test blank creation with privkeys disabled and then encryption")
|
||||||
|
self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
|
||||||
|
w5 = node.get_wallet_rpc('w5')
|
||||||
|
assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
|
||||||
|
# Encrypt the wallet
|
||||||
|
w5.encryptwallet('pass')
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
|
||||||
|
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
CreateWalletTest().main()
|
|
@ -1,46 +0,0 @@
|
||||||
#!/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_equal,
|
|
||||||
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 skip_test_if_missing_module(self):
|
|
||||||
self.skip_if_no_wallet()
|
|
||||||
|
|
||||||
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'])
|
|
||||||
|
|
||||||
self.log.info('Test that private keys cannot be imported')
|
|
||||||
addr = w2.getnewaddress('', 'legacy')
|
|
||||||
privkey = w2.dumpprivkey(addr)
|
|
||||||
assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
|
|
||||||
result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
|
|
||||||
assert(not result[0]['success'])
|
|
||||||
assert('warning' not in result[0])
|
|
||||||
assert_equal(result[0]['error']['code'], -4)
|
|
||||||
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
DisablePrivateKeysTest().main()
|
|
Loading…
Reference in a new issue