From 5a5478669c487963e99491112abc065c59973966 Mon Sep 17 00:00:00 2001 From: mtg0001 <106211915+mtg0001@users.noreply.github.com> Date: Sat, 1 Aug 2026 21:43:45 -0300 Subject: Keep new phrase addresses recoverable (#102) -newphrase installed a seed but left the wallet's visible address as the pre-seed random key and the old random pool intact, so the next address handed out -- a mining payout included -- could be one the twelve words cannot reproduce, while the window said a recovery phrase existed. Installing a phrase now clears the stale pool records (private keys stay in wallet.dat), reserves a derived default receiving key, tolerates already-stored derived keys so repeated installs do not stall the pool, and -restorephrase reports what it recovered rather than the wallet's total transaction count. Self-test covers installing a phrase over a pre-existing random pool. --- src/main.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index e5754e6..2c4e346 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -167,6 +167,20 @@ bool AddKey(const CKey& key) return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); } +static bool WalletAlreadyHasKey(const vector& vchPubKey) +{ + CRITICAL_BLOCK(cs_mapKeys) + return mapKeys.count(vchPubKey) > 0; + return false; +} + +static bool AddKeyIfMissing(const CKey& key) +{ + if (WalletAlreadyHasKey(key.GetPubKey())) + return true; + return AddKey(key); +} + vector GenerateNewKey() { CKey key; @@ -183,6 +197,23 @@ vector vchHDMaster; vector vchHDChainCode; unsigned int nHDNext = 0; +static bool ClearKeyPoolRecords(string& strErrorRet) +{ + CWalletDB walletdb; + for (map >::const_iterator mi = mapKeyPool.begin(); + mi != mapKeyPool.end(); ++mi) + { + if (!walletdb.ErasePool(mi->first)) + { + strErrorRet = strprintf("could not clear old key-pool entry %lld", + (long long)mi->first); + return false; + } + } + mapKeyPool.clear(); + return true; +} + bool DeriveHDKey(unsigned int nIndex, CKey& keyRet, string& strErrorRet) { if (!HaveHDSeed()) @@ -223,9 +254,12 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) CRITICAL_BLOCK(cs_keyPool) { - // Written before the globals change, so a failed write leaves the - // wallet on the seed it already had rather than on one that exists - // only in memory. + // Old private keys stay in wallet.dat, but old unused pool records must + // not survive the seed. Otherwise the next address can still be random + // while the UI says a recovery phrase exists. + if (!ClearKeyPoolRecords(strErrorRet)) + return false; + if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || !CWalletDB().WriteHDNext(0)) { @@ -235,6 +269,25 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet) vchHDMaster = master.privateKey; vchHDChainCode = master.chainCode; nHDNext = 0; + + CKey key; + if (!DeriveHDKey(0, key, strErrorRet)) + return false; + if (!AddKeyIfMissing(key)) + { + strErrorRet = "could not write the default derived key to wallet.dat"; + return false; + } + vector vchPubKey = key.GetPubKey(); + if (!CWalletDB().WriteDefaultKey(vchPubKey) || + !SetAddressBookName(PubKeyToAddress(vchPubKey), "Your Address") || + !CWalletDB().WriteHDNext(1)) + { + strErrorRet = "could not set the default derived receiving key"; + return false; + } + keyUser = key; + nHDNext = 1; } return true; } @@ -279,7 +332,7 @@ void TopUpKeyPool() // AddKey writes the private key. Order matters: the key is in // wallet.dat before anything can point at it, so a crash between // the two writes costs an unused key, never a usable one. - if (!AddKey(key)) + if (!AddKeyIfMissing(key)) return; vector vchPubKey = key.GetPubKey(); if (!CWalletDB().WritePool(nIndex, vchPubKey)) -- cgit v1.2.3