Skip to content

Commit f4b00b7

Browse files
committed
Import public keys in order
Do public key imports in the order that they are specified in the import or in the descriptor range.
1 parent 9e1551b commit f4b00b7

File tree

2 files changed

+62
-29
lines changed

2 files changed

+62
-29
lines changed

src/wallet/rpcdump.cpp

Lines changed: 37 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+
}
11561158

1157-
for (const auto& x : out_keys.scripts) {
1158-
import_data.import_scripts.emplace(x.second);
1159+
for (const auto& x : out_keys.scripts) {
1160+
import_data.import_scripts.emplace(x.second);
1161+
}
1162+
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);
@@ -1218,14 +1222,15 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12181222
std::map<CKeyID, CPubKey> pubkey_map;
12191223
std::map<CKeyID, CKey> privkey_map;
12201224
std::set<CScript> script_pub_keys;
1225+
std::vector<CKeyID> ordered_pubkeys;
12211226
bool have_solving_data;
12221227

12231228
if (data.exists("scriptPubKey") && data.exists("desc")) {
12241229
throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
12251230
} else if (data.exists("scriptPubKey")) {
1226-
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);
12271232
} else if (data.exists("desc")) {
1228-
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);
12291234
} else {
12301235
throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
12311236
}
@@ -1247,25 +1252,28 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
12471252
for (const auto& entry : import_data.import_scripts) {
12481253
if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) {
12491254
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;
12501273
}
1251-
}
1252-
for (const auto& entry : privkey_map) {
1253-
const CKey& key = entry.second;
1254-
CPubKey pubkey = key.GetPubKey();
1255-
const CKeyID& id = entry.first;
1256-
assert(key.VerifyPubKey(pubkey));
1257-
pwallet->mapKeyMetadata[id].nCreateTime = timestamp;
1258-
// If the private key is not present in the wallet, insert it.
1259-
if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) {
1260-
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1261-
}
1262-
pwallet->UpdateTimeFirstKey(timestamp);
1263-
}
1264-
for (const auto& entry : pubkey_map) {
1265-
const CPubKey& pubkey = entry.second;
1266-
const CKeyID& id = entry.first;
1267-
CPubKey temp;
1268-
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)) {
12691277
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
12701278
}
12711279
const auto& key_orig_it = import_data.key_origins.find(id);

test/functional/wallet_importmulti.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,5 +777,30 @@ def run_test(self):
777777
assert_equal(result[0]['error']['code'], -8)
778778
assert_equal(result[0]['error']['message'], "Keys can only be imported to the keypool when private keys are disabled")
779779

780+
# Make sure ranged imports import keys in order
781+
self.log.info('Key ranges should be imported in order')
782+
wrpc = self.nodes[1].get_wallet_rpc("noprivkeys")
783+
assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0)
784+
assert_equal(wrpc.getwalletinfo()["private_keys_enabled"], False)
785+
xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
786+
addresses = [
787+
'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0
788+
'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1
789+
'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2
790+
'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
791+
'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
792+
]
793+
result = wrpc.importmulti(
794+
[{
795+
'desc': 'wpkh([80002067/0h/0h]' + xpub + '/*)',
796+
'keypool': True,
797+
'timestamp': 'now',
798+
'range' : {'start': 0, 'end': 4}
799+
}]
800+
)
801+
for i in range(0, 5):
802+
addr = wrpc.getnewaddress('', 'bech32')
803+
assert_equal(addr, addresses[i])
804+
780805
if __name__ == '__main__':
781806
ImportMultiTest().main()

0 commit comments

Comments
 (0)