12
12
#include < util/translation.h>
13
13
#include < wallet/scriptpubkeyman.h>
14
14
15
+ // ! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
16
+ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000 ;
17
+
15
18
bool LegacyScriptPubKeyMan::GetNewDestination (const OutputType type, CTxDestination& dest, std::string& error)
16
19
{
17
20
LOCK (cs_KeyStore);
@@ -295,20 +298,72 @@ bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool i
295
298
return true ;
296
299
}
297
300
301
+ bool LegacyScriptPubKeyMan::TopUpInactiveHDChain (const CKeyID seed_id, int64_t index, bool internal)
302
+ {
303
+ LOCK (cs_KeyStore);
304
+
305
+ if (m_storage.IsLocked ()) return false ;
306
+
307
+ auto it = m_inactive_hd_chains.find (seed_id);
308
+ if (it == m_inactive_hd_chains.end ()) {
309
+ return false ;
310
+ }
311
+
312
+ CHDChain& chain = it->second ;
313
+
314
+ // Top up key pool
315
+ int64_t target_size = std::max (gArgs .GetArg (" -keypool" , DEFAULT_KEYPOOL_SIZE), (int64_t ) 1 );
316
+
317
+ // "size" of the keypools. Not really the size, actually the difference between index and the chain counter
318
+ // Since chain counter is 1 based and index is 0 based, one of them needs to be offset by 1.
319
+ int64_t kp_size = (internal ? chain.nInternalChainCounter : chain.nExternalChainCounter ) - (index + 1 );
320
+
321
+ // make sure the keypool fits the user-selected target (-keypool)
322
+ int64_t missing = std::max (target_size - kp_size, (int64_t ) 0 );
323
+
324
+ if (missing > 0 ) {
325
+ WalletBatch batch (m_storage.GetDatabase ());
326
+ for (int64_t i = missing; i > 0 ; --i) {
327
+ GenerateNewKey (batch, chain, internal);
328
+ }
329
+ if (internal) {
330
+ WalletLogPrintf (" inactive seed with id %s added %d internal keys\n " , HexStr (seed_id), missing);
331
+ } else {
332
+ WalletLogPrintf (" inactive seed with id %s added %d keys\n " , HexStr (seed_id), missing);
333
+ }
334
+ }
335
+ return true ;
336
+ }
337
+
298
338
void LegacyScriptPubKeyMan::MarkUnusedAddresses (const CScript& script)
299
339
{
300
340
LOCK (cs_KeyStore);
301
341
// extract addresses and check if they match with an unused keypool key
302
342
for (const auto & keyid : GetAffectedKeys (script, *this )) {
303
343
std::map<CKeyID, int64_t >::const_iterator mi = m_pool_key_to_index.find (keyid);
304
344
if (mi != m_pool_key_to_index.end ()) {
305
- WalletLogPrintf (" %s: Detected a used keypool key, mark all keypool key up to this key as used\n " , __func__);
345
+ WalletLogPrintf (" %s: Detected a used keypool key, mark all keypool keys up to this key as used\n " , __func__);
306
346
MarkReserveKeysAsUsed (mi->second );
307
347
308
348
if (!TopUp ()) {
309
349
WalletLogPrintf (" %s: Topping up keypool failed (locked wallet)\n " , __func__);
310
350
}
311
351
}
352
+
353
+ // Find the key's metadata and check if it's seed id (if it has one) is inactive, i.e. it is not the current m_hd_chain seed id.
354
+ // If so, TopUp the inactive hd chain
355
+ auto it = mapKeyMetadata.find (keyid);
356
+ if (it != mapKeyMetadata.end ()){
357
+ CKeyMetadata meta = it->second ;
358
+ if (!meta.hd_seed_id .IsNull () && meta.hd_seed_id != m_hd_chain.seed_id ) {
359
+ bool internal = (meta.key_origin .path [1 ] & ~BIP32_HARDENED_KEY_LIMIT) != 0 ;
360
+ int64_t index = meta.key_origin .path [2 ] & ~BIP32_HARDENED_KEY_LIMIT;
361
+
362
+ if (!TopUpInactiveHDChain (meta.hd_seed_id , index, internal)) {
363
+ WalletLogPrintf (" %s: Adding inactive seed keys failed\n " , __func__);
364
+ }
365
+ }
366
+ }
312
367
}
313
368
}
314
369
@@ -362,7 +417,7 @@ bool LegacyScriptPubKeyMan::SetupGeneration(bool force)
362
417
363
418
bool LegacyScriptPubKeyMan::IsHDEnabled () const
364
419
{
365
- return !hdChain .seed_id .IsNull ();
420
+ return !m_hd_chain .seed_id .IsNull ();
366
421
}
367
422
368
423
bool LegacyScriptPubKeyMan::CanGetAddresses (bool internal) const
@@ -848,10 +903,27 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim
848
903
void LegacyScriptPubKeyMan::SetHDChain (const CHDChain& chain, bool memonly)
849
904
{
850
905
LOCK (cs_KeyStore);
851
- if (!memonly && !WalletBatch (m_storage.GetDatabase ()).WriteHDChain (chain))
852
- throw std::runtime_error (std::string (__func__) + " : writing chain failed" );
906
+ // memonly == true means we are loading the wallet file
907
+ // memonly == false means that the chain is actually being changed
908
+ if (!memonly) {
909
+ // Store the new chain
910
+ if (!WalletBatch (m_storage.GetDatabase ()).WriteHDChain (chain)) {
911
+ throw std::runtime_error (std::string (__func__) + " : writing chain failed" );
912
+ }
913
+ // When there's an old chain, add it as an inactive chain as we are now rotating hd chains
914
+ if (!m_hd_chain.seed_id .IsNull ()) {
915
+ AddInactiveHDChain (m_hd_chain);
916
+ }
917
+ }
853
918
854
- hdChain = chain;
919
+ m_hd_chain = chain;
920
+ }
921
+
922
+ void LegacyScriptPubKeyMan::AddInactiveHDChain (const CHDChain& chain)
923
+ {
924
+ LOCK (cs_KeyStore);
925
+ assert (!chain.seed_id .IsNull ());
926
+ m_inactive_hd_chains[chain.seed_id ] = chain;
855
927
}
856
928
857
929
bool LegacyScriptPubKeyMan::HaveKey (const CKeyID &address) const
@@ -930,7 +1002,7 @@ bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyO
930
1002
return GetWatchPubKey (address, vchPubKeyOut);
931
1003
}
932
1004
933
- CPubKey LegacyScriptPubKeyMan::GenerateNewKey (WalletBatch &batch, bool internal)
1005
+ CPubKey LegacyScriptPubKeyMan::GenerateNewKey (WalletBatch &batch, CHDChain& hd_chain, bool internal)
934
1006
{
935
1007
assert (!m_storage.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS));
936
1008
assert (!m_storage.IsWalletFlagSet (WALLET_FLAG_BLANK_WALLET));
@@ -945,7 +1017,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
945
1017
946
1018
// use HD key derivation if HD was enabled during wallet creation and a seed is present
947
1019
if (IsHDEnabled ()) {
948
- DeriveNewChildKey (batch, metadata, secret, (m_storage.CanSupportFeature (FEATURE_HD_SPLIT) ? internal : false ));
1020
+ DeriveNewChildKey (batch, metadata, secret, hd_chain, (m_storage.CanSupportFeature (FEATURE_HD_SPLIT) ? internal : false ));
949
1021
} else {
950
1022
secret.MakeNewKey (fCompressed );
951
1023
}
@@ -967,9 +1039,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
967
1039
return pubkey;
968
1040
}
969
1041
970
- const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000 ;
971
-
972
- void LegacyScriptPubKeyMan::DeriveNewChildKey (WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
1042
+ void LegacyScriptPubKeyMan::DeriveNewChildKey (WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal)
973
1043
{
974
1044
// for now we use a fixed keypath scheme of m/0'/0'/k
975
1045
CKey seed; // seed (256bit)
@@ -979,7 +1049,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
979
1049
CExtKey childKey; // key at m/0'/0'/<n>'
980
1050
981
1051
// try to get the seed
982
- if (!GetKey (hdChain .seed_id , seed))
1052
+ if (!GetKey (hd_chain .seed_id , seed))
983
1053
throw std::runtime_error (std::string (__func__) + " : seed not found" );
984
1054
985
1055
masterKey.SetSeed (seed.begin (), seed.size ());
@@ -998,30 +1068,30 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
998
1068
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
999
1069
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
1000
1070
if (internal) {
1001
- chainChildKey.Derive (childKey, hdChain .nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1002
- metadata.hdKeypath = " m/0'/1'/" + ToString (hdChain .nInternalChainCounter ) + " '" ;
1071
+ chainChildKey.Derive (childKey, hd_chain .nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1072
+ metadata.hdKeypath = " m/0'/1'/" + ToString (hd_chain .nInternalChainCounter ) + " '" ;
1003
1073
metadata.key_origin .path .push_back (0 | BIP32_HARDENED_KEY_LIMIT);
1004
1074
metadata.key_origin .path .push_back (1 | BIP32_HARDENED_KEY_LIMIT);
1005
- metadata.key_origin .path .push_back (hdChain .nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1006
- hdChain .nInternalChainCounter ++;
1075
+ metadata.key_origin .path .push_back (hd_chain .nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1076
+ hd_chain .nInternalChainCounter ++;
1007
1077
}
1008
1078
else {
1009
- chainChildKey.Derive (childKey, hdChain .nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1010
- metadata.hdKeypath = " m/0'/0'/" + ToString (hdChain .nExternalChainCounter ) + " '" ;
1079
+ chainChildKey.Derive (childKey, hd_chain .nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1080
+ metadata.hdKeypath = " m/0'/0'/" + ToString (hd_chain .nExternalChainCounter ) + " '" ;
1011
1081
metadata.key_origin .path .push_back (0 | BIP32_HARDENED_KEY_LIMIT);
1012
1082
metadata.key_origin .path .push_back (0 | BIP32_HARDENED_KEY_LIMIT);
1013
- metadata.key_origin .path .push_back (hdChain .nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1014
- hdChain .nExternalChainCounter ++;
1083
+ metadata.key_origin .path .push_back (hd_chain .nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
1084
+ hd_chain .nExternalChainCounter ++;
1015
1085
}
1016
1086
} while (HaveKey (childKey.key .GetPubKey ().GetID ()));
1017
1087
secret = childKey.key ;
1018
- metadata.hd_seed_id = hdChain .seed_id ;
1088
+ metadata.hd_seed_id = hd_chain .seed_id ;
1019
1089
CKeyID master_id = masterKey.key .GetPubKey ().GetID ();
1020
1090
std::copy (master_id.begin (), master_id.begin () + 4 , metadata.key_origin .fingerprint );
1021
1091
metadata.has_key_origin = true ;
1022
1092
// update the chain model in the database
1023
- if (!batch.WriteHDChain (hdChain ))
1024
- throw std::runtime_error (std::string (__func__) + " : Writing HD chain model failed" );
1093
+ if (hd_chain. seed_id == m_hd_chain. seed_id && !batch.WriteHDChain (hd_chain ))
1094
+ throw std::runtime_error (std::string (__func__) + " : writing HD chain model failed" );
1025
1095
}
1026
1096
1027
1097
void LegacyScriptPubKeyMan::LoadKeyPool (int64_t nIndex, const CKeyPool &keypool)
@@ -1176,7 +1246,7 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
1176
1246
internal = true ;
1177
1247
}
1178
1248
1179
- CPubKey pubkey (GenerateNewKey (batch, internal));
1249
+ CPubKey pubkey (GenerateNewKey (batch, m_hd_chain, internal));
1180
1250
AddKeypoolPubkeyWithDB (pubkey, internal, batch);
1181
1251
}
1182
1252
if (missingInternal + missingExternal > 0 ) {
@@ -1249,7 +1319,7 @@ bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType typ
1249
1319
if (!ReserveKeyFromKeyPool (nIndex, keypool, internal) && !m_storage.IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1250
1320
if (m_storage.IsLocked ()) return false ;
1251
1321
WalletBatch batch (m_storage.GetDatabase ());
1252
- result = GenerateNewKey (batch, internal);
1322
+ result = GenerateNewKey (batch, m_hd_chain, internal);
1253
1323
return true ;
1254
1324
}
1255
1325
KeepDestination (nIndex, type);
0 commit comments