Skip to content

Commit c576979

Browse files
committed
Merge #14075: Import watch only pubkeys to the keypool if private keys are disabled
f4b00b7 Import public keys in order (Andrew Chow) 9e1551b Test pubkey import to keypool (Andrew Chow) 513719c Add option to importmulti add an imported pubkey to the keypool (Andrew Chow) 9b81fd1 Fetch keys from keypool when private keys are disabled (Andrew Chow) 99cccb9 Add a method to add a pubkey to the keypool (Andrew Chow) Pull request description: If the wallet has private keys disabled, allow importing public keys into the keypool. A `keypool` option has been added to `importmulti` in order to signal that the keys should be added to the keypool. Tree-SHA512: e88ea7bf726c13031aa739389a0c2662e6b22a4f9a4dc45b042418c692a950d98f170e0db80eb59e9c9063cda8765eaa85b2927d1790b9625744f7a87bad5fc8
2 parents 8d0ec74 + f4b00b7 commit c576979

File tree

6 files changed

+202
-68
lines changed

6 files changed

+202
-68
lines changed

src/wallet/rpcdump.cpp

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
967967
}
968968
}
969969

970-
static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
970+
static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
971971
{
972972
UniValue warnings(UniValue::VARR);
973973

@@ -1038,6 +1038,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
10381038
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
10391039
}
10401040
pubkey_map.emplace(pubkey.GetID(), pubkey);
1041+
ordered_pubkeys.push_back(pubkey.GetID());
10411042
}
10421043
for (size_t i = 0; i < keys.size(); ++i) {
10431044
const auto& str = keys[i].get_str();
@@ -1110,7 +1111,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
11101111
return warnings;
11111112
}
11121113

1113-
static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
1114+
static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys)
11141115
{
11151116
UniValue warnings(UniValue::VARR);
11161117

@@ -1144,22 +1145,25 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
11441145

11451146
const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
11461147

1147-
FlatSigningProvider out_keys;
1148-
11491148
// Expand all descriptors to get public keys and scripts.
11501149
// TODO: get private keys from descriptors too
11511150
for (int i = range_start; i <= range_end; ++i) {
1151+
FlatSigningProvider out_keys;
11521152
std::vector<CScript> scripts_temp;
11531153
parsed_desc->Expand(i, keys, scripts_temp, out_keys);
11541154
std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
1155-
}
1155+
for (const auto& key_pair : out_keys.pubkeys) {
1156+
ordered_pubkeys.push_back(key_pair.first);
1157+
}
1158+
1159+
for (const auto& x : out_keys.scripts) {
1160+
import_data.import_scripts.emplace(x.second);
1161+
}
11561162

1157-
for (const auto& x : out_keys.scripts) {
1158-
import_data.import_scripts.emplace(x.second);
1163+
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
1164+
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
11591165
}
11601166

1161-
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
1162-
import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
11631167
for (size_t i = 0; i < priv_keys.size(); ++i) {
11641168
const auto& str = priv_keys[i].get_str();
11651169
CKey key = DecodeSecret(str);
@@ -1207,19 +1211,26 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12071211
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
12081212
}
12091213
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
1214+
const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
1215+
1216+
// Add to keypool only works with privkeys disabled
1217+
if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1218+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled");
1219+
}
12101220

12111221
ImportData import_data;
12121222
std::map<CKeyID, CPubKey> pubkey_map;
12131223
std::map<CKeyID, CKey> privkey_map;
12141224
std::set<CScript> script_pub_keys;
1225+
std::vector<CKeyID> ordered_pubkeys;
12151226
bool have_solving_data;
12161227

12171228
if (data.exists("scriptPubKey") && data.exists("desc")) {
12181229
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
12191230
} else if (data.exists("scriptPubKey")) {
1220-
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
1231+
warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
12211232
} else if (data.exists("desc")) {
1222-
warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
1233+
warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
12231234
} else {
12241235
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
12251236
}
@@ -1241,32 +1252,40 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12411252
for (const auto& entry : import_data.import_scripts) {
12421253
if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) {
12431254
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet");
1255+
}
1256+
}
1257+
for (const auto& entry : privkey_map) {
1258+
const CKey& key = entry.second;
1259+
CPubKey pubkey = key.GetPubKey();
1260+
const CKeyID& id = entry.first;
1261+
assert(key.VerifyPubKey(pubkey));
1262+
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1263+
// If the private key is not present in the wallet, insert it.
1264+
if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) {
1265+
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1266+
}
1267+
pwallet->UpdateTimeFirstKey(timestamp);
1268+
}
1269+
for (const CKeyID& id : ordered_pubkeys) {
1270+
auto entry = pubkey_map.find(id);
1271+
if (entry == pubkey_map.end()) {
1272+
continue;
12441273
}
1245-
}
1246-
for (const auto& entry : privkey_map) {
1247-
const CKey& key = entry.second;
1248-
CPubKey pubkey = key.GetPubKey();
1249-
const CKeyID& id = entry.first;
1250-
assert(key.VerifyPubKey(pubkey));
1251-
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1252-
// If the private key is not present in the wallet, insert it.
1253-
if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) {
1254-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1255-
}
1256-
pwallet->UpdateTimeFirstKey(timestamp);
1257-
}
1258-
for (const auto& entry : pubkey_map) {
1259-
const CPubKey& pubkey = entry.second;
1260-
const CKeyID& id = entry.first;
1261-
CPubKey temp;
1262-
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
1274+
const CPubKey& pubkey = entry->second;
1275+
CPubKey temp;
1276+
if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) {
12631277
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
12641278
}
12651279
const auto& key_orig_it = import_data.key_origins.find(id);
12661280
if (key_orig_it != import_data.key_origins.end()) {
12671281
pwallet->AddKeyOrigin(pubkey, key_orig_it->second);
12681282
}
12691283
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1284+
1285+
// Add to keypool only works with pubkeys
1286+
if (add_keypool) {
1287+
pwallet->AddKeypoolPubkey(pubkey, internal);
1288+
}
12701289
}
12711290

12721291
for (const CScript& script : script_pub_keys) {

src/wallet/rpcwallet.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,18 +173,12 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
173173
},
174174
}.ToString());
175175

176-
// Belt and suspenders check for disabled private keys
177-
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
178-
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
179-
}
180-
181176
LOCK(pwallet->cs_wallet);
182177

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

187-
188182
// Parse the label first so we don't generate a key if there's an error
189183
std::string label;
190184
if (!request.params[0].isNull())
@@ -240,11 +234,6 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
240234
},
241235
}.ToString());
242236

243-
// Belt and suspenders check for disabled private keys
244-
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
245-
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
246-
}
247-
248237
LOCK(pwallet->cs_wallet);
249238

250239
if (!pwallet->CanGetAddresses(true)) {
@@ -2447,7 +2436,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request)
24472436
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
24482437
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
24492438
CKeyID seed_id = pwallet->GetHDChain().seed_id;
2450-
if (!seed_id.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
2439+
if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
24512440
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
24522441
}
24532442
if (pwallet->IsCrypted()) {

src/wallet/wallet.cpp

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,8 +2833,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
28332833
// post-backup change.
28342834

28352835
// Reserve a new key pair from key pool
2836-
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
2837-
strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
2836+
if (!CanGetAddresses(true)) {
2837+
strFailReason = _("Can't generate a change-address key. No keys in the internal keypool and can't generate any keys.");
28382838
return false;
28392839
}
28402840
CPubKey vchPubKey;
@@ -3443,20 +3443,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
34433443
internal = true;
34443444
}
34453445

3446-
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
3447-
int64_t index = ++m_max_keypool_index;
3448-
34493446
CPubKey pubkey(GenerateNewKey(batch, internal));
3450-
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
3451-
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
3452-
}
3453-
3454-
if (internal) {
3455-
setInternalKeyPool.insert(index);
3456-
} else {
3457-
setExternalKeyPool.insert(index);
3458-
}
3459-
m_pool_key_to_index[pubkey.GetID()] = index;
3447+
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
34603448
}
34613449
if (missingInternal + missingExternal > 0) {
34623450
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
@@ -3466,6 +3454,29 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
34663454
return true;
34673455
}
34683456

3457+
void CWallet::AddKeypoolPubkey(const CPubKey& pubkey, const bool internal)
3458+
{
3459+
WalletBatch batch(*database);
3460+
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
3461+
NotifyCanGetAddressesChanged();
3462+
}
3463+
3464+
void CWallet::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
3465+
{
3466+
LOCK(cs_wallet);
3467+
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
3468+
int64_t index = ++m_max_keypool_index;
3469+
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
3470+
throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed");
3471+
}
3472+
if (internal) {
3473+
setInternalKeyPool.insert(index);
3474+
} else {
3475+
setExternalKeyPool.insert(index);
3476+
}
3477+
m_pool_key_to_index[pubkey.GetID()] = index;
3478+
}
3479+
34693480
bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
34703481
{
34713482
nIndex = -1;
@@ -3476,7 +3487,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
34763487
if (!IsLocked())
34773488
TopUpKeyPool();
34783489

3479-
bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
3490+
bool fReturningInternal = fRequestedInternal;
3491+
fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
34803492
bool use_split_keypool = set_pre_split_keypool.empty();
34813493
std::set<int64_t>& setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool;
34823494

@@ -3493,7 +3505,8 @@ bool CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
34933505
if (!batch.ReadPool(nIndex, keypool)) {
34943506
throw std::runtime_error(std::string(__func__) + ": read failed");
34953507
}
3496-
if (!HaveKey(keypool.vchPubKey.GetID())) {
3508+
CPubKey pk;
3509+
if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
34973510
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
34983511
}
34993512
// If the key was pre-split keypool, we don't care about what type it is
@@ -3547,7 +3560,7 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
35473560
{
35483561
LOCK(cs_wallet);
35493562
int64_t nIndex;
3550-
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
3563+
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
35513564
if (IsLocked()) return false;
35523565
WalletBatch batch(*database);
35533566
result = GenerateNewKey(batch, internal);

src/wallet/wallet.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface
10011001
bool NewKeyPool();
10021002
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
10031003
bool TopUpKeyPool(unsigned int kpSize = 0);
1004+
void AddKeypoolPubkey(const CPubKey& pubkey, const bool internal);
1005+
void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch);
10041006

10051007
/**
10061008
* Reserves a key from the keypool and sets nIndex to its index

test/functional/wallet_createwallet.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ def run_test(self):
3131
self.log.info("Test disableprivatekeys creation.")
3232
self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
3333
w1 = node.get_wallet_rpc('w1')
34-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getnewaddress)
35-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w1.getrawchangeaddress)
34+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress)
35+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress)
3636
w1.importpubkey(w0.getaddressinfo(address1)['pubkey'])
3737

3838
self.log.info('Test that private keys cannot be imported')
@@ -48,8 +48,8 @@ def run_test(self):
4848
self.log.info("Test blank creation with private keys disabled.")
4949
self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
5050
w2 = node.get_wallet_rpc('w2')
51-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getnewaddress)
52-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w2.getrawchangeaddress)
51+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress)
52+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress)
5353
w2.importpubkey(w0.getaddressinfo(address1)['pubkey'])
5454

5555
self.log.info("Test blank creation with private keys enabled.")
@@ -89,12 +89,12 @@ def run_test(self):
8989
self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
9090
w5 = node.get_wallet_rpc('w5')
9191
assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
92-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
93-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
92+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
93+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
9494
# Encrypt the wallet
9595
w5.encryptwallet('pass')
96-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getnewaddress)
97-
assert_raises_rpc_error(-4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress)
96+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
97+
assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
9898

9999
if __name__ == '__main__':
100100
CreateWalletTest().main()

0 commit comments

Comments
 (0)