Skip to content

Commit 3e38d40

Browse files
committed
Merge #15235: Do not import private keys to wallets with private keys disabled
e6c58d3 Do not import private keys to wallets with private keys disabled (Andrew Chow) b5c5021 Refactor importwallet to extract data from the file and then import (Andrew Chow) 1f77f67 tests: unify RPC argument to cli argument conversion and handle dicts and lists (Andrew Chow) Pull request description: Fixes a bug where private keys could be imported to wallets with private keys disabled. Now every RPC which can import private keys checks for whether the wallet has private keys are disabled and errors if it is. Also added an belt-and-suspenders check to `AddKeyPubkeyWithDB` to have it assert that the wallet has private keys enabled. Tree-SHA512: 5cd04febce9aa2bd9bfd02f312c6ff8705e37278cae59efd3895f6d6e2f1b477aefd297e2dd0860791bdd3d4f3cad8eb1a404f8f3d4e2035b91314ad2c1028ae
2 parents cb35f1d + e6c58d3 commit 3e38d40

File tree

4 files changed

+86
-34
lines changed

4 files changed

+86
-34
lines changed

src/wallet/rpcdump.cpp

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ UniValue importprivkey(const JSONRPCRequest& request)
134134
},
135135
}.ToString());
136136

137+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
138+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
139+
}
137140

138141
WalletRescanReserver reserver(pwallet);
139142
bool fRescan = true;
@@ -587,8 +590,10 @@ UniValue importwallet(const JSONRPCRequest& request)
587590
// Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
588591
// we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
589592
uiInterface.ShowProgress(strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI
593+
std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
594+
std::vector<std::pair<CScript, int64_t>> scripts;
590595
while (file.good()) {
591-
uiInterface.ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
596+
uiInterface.ShowProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
592597
std::string line;
593598
std::getline(file, line);
594599
if (line.empty() || line[0] == '#')
@@ -600,13 +605,6 @@ UniValue importwallet(const JSONRPCRequest& request)
600605
continue;
601606
CKey key = DecodeSecret(vstr[0]);
602607
if (key.IsValid()) {
603-
CPubKey pubkey = key.GetPubKey();
604-
assert(key.VerifyPubKey(pubkey));
605-
CKeyID keyid = pubkey.GetID();
606-
if (pwallet->HaveKey(keyid)) {
607-
pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
608-
continue;
609-
}
610608
int64_t nTime = DecodeDumpTime(vstr[1]);
611609
std::string strLabel;
612610
bool fLabel = true;
@@ -622,36 +620,67 @@ UniValue importwallet(const JSONRPCRequest& request)
622620
fLabel = true;
623621
}
624622
}
625-
pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid));
626-
if (!pwallet->AddKeyPubKey(key, pubkey)) {
627-
fGood = false;
628-
continue;
629-
}
630-
pwallet->mapKeyMetadata[keyid].nCreateTime = nTime;
631-
if (fLabel)
632-
pwallet->SetAddressBook(keyid, strLabel, "receive");
633-
nTimeBegin = std::min(nTimeBegin, nTime);
623+
keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel));
634624
} else if(IsHex(vstr[0])) {
635-
std::vector<unsigned char> vData(ParseHex(vstr[0]));
636-
CScript script = CScript(vData.begin(), vData.end());
637-
CScriptID id(script);
638-
if (pwallet->HaveCScript(id)) {
639-
pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", vstr[0]);
640-
continue;
641-
}
642-
if(!pwallet->AddCScript(script)) {
643-
pwallet->WalletLogPrintf("Error importing script %s\n", vstr[0]);
644-
fGood = false;
645-
continue;
646-
}
647-
int64_t birth_time = DecodeDumpTime(vstr[1]);
648-
if (birth_time > 0) {
649-
pwallet->m_script_metadata[id].nCreateTime = birth_time;
650-
nTimeBegin = std::min(nTimeBegin, birth_time);
651-
}
625+
std::vector<unsigned char> vData(ParseHex(vstr[0]));
626+
CScript script = CScript(vData.begin(), vData.end());
627+
int64_t birth_time = DecodeDumpTime(vstr[1]);
628+
scripts.push_back(std::pair<CScript, int64_t>(script, birth_time));
652629
}
653630
}
654631
file.close();
632+
// We now know whether we are importing private keys, so we can error if private keys are disabled
633+
if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
634+
uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
635+
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled");
636+
}
637+
double total = (double)(keys.size() + scripts.size());
638+
double progress = 0;
639+
for (const auto& key_tuple : keys) {
640+
uiInterface.ShowProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
641+
const CKey& key = std::get<0>(key_tuple);
642+
int64_t time = std::get<1>(key_tuple);
643+
bool has_label = std::get<2>(key_tuple);
644+
std::string label = std::get<3>(key_tuple);
645+
646+
CPubKey pubkey = key.GetPubKey();
647+
assert(key.VerifyPubKey(pubkey));
648+
CKeyID keyid = pubkey.GetID();
649+
if (pwallet->HaveKey(keyid)) {
650+
pwallet->WalletLogPrintf("Skipping import of %s (key already present)\n", EncodeDestination(keyid));
651+
continue;
652+
}
653+
pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(keyid));
654+
if (!pwallet->AddKeyPubKey(key, pubkey)) {
655+
fGood = false;
656+
continue;
657+
}
658+
pwallet->mapKeyMetadata[keyid].nCreateTime = time;
659+
if (has_label)
660+
pwallet->SetAddressBook(keyid, label, "receive");
661+
nTimeBegin = std::min(nTimeBegin, time);
662+
progress++;
663+
}
664+
for (const auto& script_pair : scripts) {
665+
uiInterface.ShowProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
666+
const CScript& script = script_pair.first;
667+
int64_t time = script_pair.second;
668+
CScriptID id(script);
669+
if (pwallet->HaveCScript(id)) {
670+
pwallet->WalletLogPrintf("Skipping import of %s (script already present)\n", HexStr(script));
671+
continue;
672+
}
673+
if(!pwallet->AddCScript(script)) {
674+
pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script));
675+
fGood = false;
676+
continue;
677+
}
678+
if (time > 0) {
679+
pwallet->m_script_metadata[id].nCreateTime = time;
680+
nTimeBegin = std::min(nTimeBegin, time);
681+
}
682+
progress++;
683+
}
655684
uiInterface.ShowProgress("", 100, false); // hide progress dialog in GUI
656685
pwallet->UpdateTimeFirstKey(nTimeBegin);
657686
}
@@ -958,6 +987,11 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
958987
const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
959988
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
960989

990+
// If private keys are disabled, abort if private keys are being imported
991+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.isNull()) {
992+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
993+
}
994+
961995
// Generate the script and destination for the scriptPubKey provided
962996
CScript script;
963997
CTxDestination dest;

src/wallet/rpcwallet.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,6 +3872,10 @@ UniValue sethdseed(const JSONRPCRequest& request)
38723872
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download");
38733873
}
38743874

3875+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3876+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
3877+
}
3878+
38753879
auto locked_chain = pwallet->chain().lock();
38763880
LOCK(pwallet->cs_wallet);
38773881

src/wallet/wallet.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C
251251
{
252252
AssertLockHeld(cs_wallet); // mapKeyMetadata
253253

254+
// Make sure we aren't adding private keys to private key disabled wallets
255+
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
256+
254257
// CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey
255258
// which is overridden below. To avoid flushes, the database handle is
256259
// tunneled through to it.

test/functional/wallet_disableprivatekeys.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from test_framework.test_framework import BitcoinTestFramework
99
from test_framework.util import (
10+
assert_equal,
1011
assert_raises_rpc_error,
1112
)
1213

@@ -31,5 +32,15 @@ def run_test(self):
3132
assert_raises_rpc_error(-4,"Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
3233
w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey'])
3334

35+
self.log.info('Test that private keys cannot be imported')
36+
addr = w2.getnewaddress('', 'legacy')
37+
privkey = w2.dumpprivkey(addr)
38+
assert_raises_rpc_error(-4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey)
39+
result = w1.importmulti([{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}])
40+
assert(not result[0]['success'])
41+
assert('warning' not in result[0])
42+
assert_equal(result[0]['error']['code'], -4)
43+
assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
44+
3445
if __name__ == '__main__':
3546
DisablePrivateKeysTest().main()

0 commit comments

Comments
 (0)