Skip to content

Commit cebefba

Browse files
committed
Add option to disable private keys during internal wallet creation
1 parent 9995a60 commit cebefba

File tree

4 files changed

+95
-28
lines changed

4 files changed

+95
-28
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
160160
+ HelpExampleRpc("getnewaddress", "")
161161
);
162162

163+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
164+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
165+
}
166+
163167
LOCK2(cs_main, pwallet->cs_wallet);
164168

165169
// Parse the label first so we don't generate a key if there's an error
@@ -267,6 +271,10 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
267271
+ HelpExampleRpc("getrawchangeaddress", "")
268272
);
269273

274+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
275+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
276+
}
277+
270278
LOCK2(cs_main, pwallet->cs_wallet);
271279

272280
if (!pwallet->IsLocked()) {
@@ -2499,6 +2507,10 @@ static UniValue keypoolrefill(const JSONRPCRequest& request)
24992507
+ HelpExampleRpc("keypoolrefill", "")
25002508
);
25012509

2510+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
2511+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
2512+
}
2513+
25022514
LOCK2(cs_main, pwallet->cs_wallet);
25032515

25042516
// 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
@@ -2983,19 +2995,20 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
29832995
"Returns an object containing various wallet state info.\n"
29842996
"\nResult:\n"
29852997
"{\n"
2986-
" \"walletname\": xxxxx, (string) the wallet name\n"
2987-
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
2988-
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2989-
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
2990-
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
2991-
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
2992-
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
2993-
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
2994-
" \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
2995-
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
2996-
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
2997-
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
2998-
" \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n"
2998+
" \"walletname\": xxxxx, (string) the wallet name\n"
2999+
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
3000+
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
3001+
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
3002+
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
3003+
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
3004+
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
3005+
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n"
3006+
" \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n"
3007+
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
3008+
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
3009+
" \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n"
3010+
" \"hdmasterkeyid\": \"<hash160>\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n"
3011+
" \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n"
29993012
"}\n"
30003013
"\nExamples:\n"
30013014
+ HelpExampleCli("getwalletinfo", "")
@@ -3031,6 +3044,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
30313044
obj.pushKV("hdseedid", seed_id.GetHex());
30323045
obj.pushKV("hdmasterkeyid", seed_id.GetHex());
30333046
}
3047+
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
30343048
return obj;
30353049
}
30363050

src/wallet/wallet.cpp

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
164164

165165
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
166166
{
167+
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
167168
AssertLockHeld(cs_wallet); // mapKeyMetadata
168169
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
169170

@@ -1465,6 +1466,7 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
14651466

14661467
CPubKey CWallet::GenerateNewSeed()
14671468
{
1469+
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
14681470
CKey key;
14691471
key.MakeNewKey(true);
14701472
return DeriveNewSeed(key);
@@ -1539,13 +1541,19 @@ bool CWallet::IsWalletFlagSet(uint64_t flag)
15391541
return (m_wallet_flags & flag);
15401542
}
15411543

1542-
void CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
1544+
bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly)
15431545
{
15441546
LOCK(cs_wallet);
15451547
m_wallet_flags = overwriteFlags;
1548+
if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ (overwriteFlags >> 32)) {
1549+
// contains unknown non-tolerable wallet flags
1550+
return false;
1551+
}
15461552
if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
15471553
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
15481554
}
1555+
1556+
return true;
15491557
}
15501558

15511559
int64_t CWalletTx::GetTxTime() const
@@ -2793,6 +2801,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransac
27932801
// post-backup change.
27942802

27952803
// Reserve a new key pair from key pool
2804+
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
2805+
strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
2806+
return false;
2807+
}
27962808
CPubKey vchPubKey;
27972809
bool ret;
27982810
ret = reservekey.GetReservedKey(vchPubKey, true);
@@ -3193,7 +3205,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
31933205
{
31943206
LOCK(cs_KeyStore);
31953207
// This wallet is in its first run if all of these are empty
3196-
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty();
3208+
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
31973209
}
31983210

31993211
if (nLoadWalletRet != DBErrors::LOAD_OK)
@@ -3317,6 +3329,9 @@ const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const
33173329
*/
33183330
bool CWallet::NewKeyPool()
33193331
{
3332+
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3333+
return false;
3334+
}
33203335
{
33213336
LOCK(cs_wallet);
33223337
WalletBatch batch(*database);
@@ -3375,6 +3390,9 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
33753390

33763391
bool CWallet::TopUpKeyPool(unsigned int kpSize)
33773392
{
3393+
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3394+
return false;
3395+
}
33783396
{
33793397
LOCK(cs_wallet);
33803398

@@ -3499,6 +3517,10 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
34993517

35003518
bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
35013519
{
3520+
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3521+
return false;
3522+
}
3523+
35023524
CKeyPool keypool;
35033525
{
35043526
LOCK(cs_wallet);
@@ -4038,7 +4060,7 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string&
40384060
return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string);
40394061
}
40404062

4041-
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
4063+
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags)
40424064
{
40434065
const std::string& walletFile = name;
40444066

@@ -4163,18 +4185,33 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(const std::string& name,
41634185
}
41644186
walletInstance->SetMinVersion(FEATURE_LATEST);
41654187

4166-
// generate a new seed
4167-
CPubKey seed = walletInstance->GenerateNewSeed();
4168-
if (!walletInstance->SetHDSeed(seed))
4169-
throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed");
4188+
if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
4189+
//selective allow to set flags
4190+
walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
4191+
} else {
4192+
// generate a new seed
4193+
CPubKey seed = walletInstance->GenerateNewSeed();
4194+
if (!walletInstance->SetHDSeed(seed)) {
4195+
throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed");
4196+
}
4197+
}
41704198

41714199
// Top up the keypool
4172-
if (!walletInstance->TopUpKeyPool()) {
4200+
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) {
41734201
InitError(_("Unable to generate initial keys") += "\n");
41744202
return nullptr;
41754203
}
41764204

41774205
walletInstance->ChainStateFlushed(chainActive.GetLocator());
4206+
} else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
4207+
// Make it impossible to disable private keys after creation
4208+
InitError(strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile));
4209+
return NULL;
4210+
} else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
4211+
LOCK(walletInstance->cs_KeyStore);
4212+
if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) {
4213+
InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile));
4214+
}
41784215
} else if (gArgs.IsArgSet("-usehd")) {
41794216
bool useHD = gArgs.GetBoolArg("-usehd", true);
41804217
if (walletInstance->IsHDEnabled() && !useHD) {

src/wallet/wallet.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@ constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT};
113113
//! Default for -changetype
114114
constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO};
115115

116+
enum WalletFlags : uint64_t {
117+
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
118+
// unkown wallet flags in the lower section <= (1 << 31) will be tolerated
119+
120+
// will enforce the rule that the wallet can't contain any private keys (only watch-only/pubkeys)
121+
WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32),
122+
};
123+
124+
static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS;
125+
116126
/** A key pool entry */
117127
class CKeyPool
118128
{
@@ -1132,7 +1142,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
11321142
static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string);
11331143

11341144
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
1135-
static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path);
1145+
static std::shared_ptr<CWallet> CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0);
11361146

11371147
/**
11381148
* Wallet post-init setup
@@ -1198,8 +1208,9 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
11981208
/** check if a certain wallet flag is set */
11991209
bool IsWalletFlagSet(uint64_t flag);
12001210

1201-
/** overwrite all flags by the given uint64_t */
1202-
void SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
1211+
/** overwrite all flags by the given uint64_t
1212+
returns false if unknown, non-tolerable flags are present */
1213+
bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly);
12031214
};
12041215

12051216
/** A key allocated from the key pool. */

src/wallet/walletdb.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
513513
} else if (strType == "flags") {
514514
uint64_t flags;
515515
ssValue >> flags;
516-
pwallet->SetWalletFlags(flags, true);
516+
if (!pwallet->SetWalletFlags(flags, true)) {
517+
strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";
518+
return false;
519+
}
517520
} else if (strType != "bestblock" && strType != "bestblock_nomerkle") {
518521
wss.m_unknown_records++;
519522
}
@@ -574,10 +577,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
574577
{
575578
// losing keys is considered a catastrophic error, anything else
576579
// we assume the user can live with:
577-
if (IsKeyType(strType) || strType == "defaultkey")
580+
if (IsKeyType(strType) || strType == "defaultkey") {
578581
result = DBErrors::CORRUPT;
579-
else
580-
{
582+
} else if(strType == "flags") {
583+
// reading the wallet flags can only fail if unknown flags are present
584+
result = DBErrors::TOO_NEW;
585+
} else {
581586
// Leave other errors alone, if we try to fix them we might make things worse.
582587
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
583588
if (strType == "tx")

0 commit comments

Comments
 (0)