upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-12 14:44:43 -0300
committerNsable-sh <nsable-sh@noreply.git.bitflash.network>2026-08-12 14:44:43 -0300
commita109dcd23fd387324e63f265a237723ba1d6c4fa (patch)
treebadb646bd1113bc21f2078dd7c427dae06ffa992
parentbc3de301d9ed0e5fb5e2034be2e066d77cf23804 (diff)
wallet: collapse restored legacy hdnext
-rw-r--r--src/selftest.cpp28
-rw-r--r--src/walletcmd.cpp62
-rw-r--r--src/walletcmd.h9
3 files changed, 93 insertions, 6 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index cca5335..bcbf9d3 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -586,6 +586,34 @@ static int RunWalletHDSelfTest()
586 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1; 586 "the recovery audit finds wallet.dat-only immature mining rewards") ? 0 : 1;
587 nFail += Check(audit.nLegacyImmatureTx == 1, 587 nFail += Check(audit.nLegacyImmatureTx == 1,
588 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1; 588 "the recovery audit counts wallet.dat-only immature mining rewards") ? 0 : 1;
589
590 CKey keyLegacyHD11;
591 nSchemaForLegacyAudit = nHDKeySchema;
592 nHDKeySchema = HD_SCHEMA_LEGACY;
593 if (!DeriveHDKey(11, keyLegacyHD11, strError))
594 throw std::runtime_error("legacy compatibility index 11 derivation failed: " + strError);
595 nHDKeySchema = nSchemaForLegacyAudit;
596 if (!AddKey(keyLegacyHD11))
597 throw std::runtime_error("could not store the legacy compatibility index 11 key");
598
599 std::map<unsigned int, std::vector<unsigned char> > mapLegacyProbe;
600 mapLegacyProbe[0] = keyAuditLegacyHD.GetPubKey();
601 mapLegacyProbe[11] = keyLegacyHD11.GetPubKey();
602
603 CWalletTx wtxLegacyHD11;
604 wtxLegacyHD11.vout.push_back(CTxOut(19 * COIN, CScript() << keyLegacyHD11.GetPubKey() << OP_CHECKSIG));
605 CRITICAL_BLOCK(cs_mapWallet)
606 {
607 mapWallet.clear();
608 mapWallet[wtxLegacyHD11.GetHash()] = wtxLegacyHD11;
609 }
610 nFail += Check(WalletLastUsedPubKeyIndexNext(mapLegacyProbe) == 12,
611 "restore collapses legacy hdnext to the last used compatibility index plus one") ? 0 : 1;
612
613 CRITICAL_BLOCK(cs_mapWallet)
614 mapWallet.clear();
615 nFail += Check(WalletLastUsedPubKeyIndexNext(mapLegacyProbe) == 0,
616 "restore does not persist legacy hdnext at the scan depth when no compatibility key was used") ? 0 : 1;
589 } 617 }
590 catch (const std::exception& e) 618 catch (const std::exception& e)
591 { 619 {
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 0ea2251..e8ac4bf 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -54,6 +54,37 @@ bool RestoreScanReachedDepth(int nSchema,
54 return nLegacyNext >= nDepth; 54 return nLegacyNext >= nDepth;
55} 55}
56 56
57unsigned int WalletLastUsedPubKeyIndexNext(
58 const std::map<unsigned int, std::vector<unsigned char> >& mapPubKeysByIndex)
59{
60 std::map<std::vector<unsigned char>, unsigned int> mapIndexByPubKey;
61 for (std::map<unsigned int, std::vector<unsigned char> >::const_iterator it =
62 mapPubKeysByIndex.begin();
63 it != mapPubKeysByIndex.end(); ++it)
64 mapIndexByPubKey[it->second] = it->first;
65
66 unsigned int nNext = 0;
67 CRITICAL_BLOCK(cs_mapWallet)
68 {
69 for (map<uint256, CWalletTx>::iterator it = mapWallet.begin();
70 it != mapWallet.end(); ++it)
71 {
72 CWalletTx& wtx = (*it).second;
73 for (int i = 0; i < (int)wtx.vout.size(); i++)
74 {
75 vector<unsigned char> vchPubKey;
76 if (!ExtractPubKey(wtx.vout[i].scriptPubKey, false, vchPubKey))
77 continue;
78 std::map<std::vector<unsigned char>, unsigned int>::const_iterator mi =
79 mapIndexByPubKey.find(vchPubKey);
80 if (mi != mapIndexByPubKey.end() && mi->second + 1 > nNext)
81 nNext = mi->second + 1;
82 }
83 }
84 }
85 return nNext;
86}
87
57int CmdNewPhrase() 88int CmdNewPhrase()
58{ 89{
59 AttachTerminal(); 90 AttachTerminal();
@@ -132,7 +163,10 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
132 // Derive forward in batches, scanning after each, until a whole batch turns 163 // Derive forward in batches, scanning after each, until a whole batch turns
133 // up nothing. Every derived key is written to the wallet before the scan, 164 // up nothing. Every derived key is written to the wallet before the scan,
134 // because the scan asks the wallet what belongs to it. 165 // because the scan asks the wallet what belongs to it.
135 int nTotalDerived = nHDKeySchema == HD_SCHEMA_BIP44 166 const bool fRestoreBIP44 = nHDKeySchema == HD_SCHEMA_BIP44;
167 unsigned int nLegacyScanNext = nHDNext;
168 std::map<unsigned int, vector<unsigned char> > mapLegacyScanPubKeys;
169 int nTotalDerived = fRestoreBIP44
136 ? (int)(nHDReceiveNext + nHDChangeNext) 170 ? (int)(nHDReceiveNext + nHDChangeNext)
137 : (int)nHDNext; 171 : (int)nHDNext;
138 int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); 172 int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN);
@@ -151,7 +185,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
151 nWalletBefore = mapWallet.size(); 185 nWalletBefore = mapWallet.size();
152 186
153 std::string strDeriveError; 187 std::string strDeriveError;
154 if (nHDKeySchema == HD_SCHEMA_BIP44) 188 if (fRestoreBIP44)
155 { 189 {
156 for (int i = 0; i < RESTORE_BATCH; i++) 190 for (int i = 0; i < RESTORE_BATCH; i++)
157 { 191 {
@@ -182,15 +216,15 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
182 for (int i = 0; i < RESTORE_BATCH; i++) 216 for (int i = 0; i < RESTORE_BATCH; i++)
183 { 217 {
184 CKey key; 218 CKey key;
185 if (!DeriveHDKey(nHDNext, key, strDeriveError)) 219 if (!DeriveHDKey(nLegacyScanNext, key, strDeriveError))
186 break; 220 break;
187 if (!AddKey(key)) 221 if (!AddKey(key))
188 break; 222 break;
189 nHDNext++; 223 mapLegacyScanPubKeys[nLegacyScanNext] = key.GetPubKey();
224 nLegacyScanNext++;
190 nTotalDerived++; 225 nTotalDerived++;
191 } 226 }
192 nHDKeySchema = nSavedSchema; 227 nHDKeySchema = nSavedSchema;
193 CWalletDB().WriteHDNext(nHDNext);
194 } 228 }
195 else 229 else
196 { 230 {
@@ -209,6 +243,22 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
209 243
210 ScanForWalletTransactions(pindexGenesisBlock); 244 ScanForWalletTransactions(pindexGenesisBlock);
211 245
246 if (fRestoreBIP44)
247 {
248 // The legacy branch is scanned only for compatibility with phrases
249 // created before BIP44. Persist the last used legacy index + 1, not
250 // the lookahead depth. Otherwise a restore with no legacy hits can
251 // leave hdnext at RESTORE_MAX and make future compatibility scans
252 // look exhausted even though no legacy key was ever used.
253 unsigned int nLegacyUsedNext =
254 WalletLastUsedPubKeyIndexNext(mapLegacyScanPubKeys);
255 if (nLegacyUsedNext != nHDNext)
256 {
257 nHDNext = nLegacyUsedNext;
258 CWalletDB().WriteHDNext(nHDNext);
259 }
260 }
261
212 size_t nWalletAfter = 0; 262 size_t nWalletAfter = 0;
213 CRITICAL_BLOCK(cs_mapWallet) 263 CRITICAL_BLOCK(cs_mapWallet)
214 nWalletAfter = mapWallet.size(); 264 nWalletAfter = mapWallet.size();
@@ -239,7 +289,7 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
239 RestoreScanReachedDepth(nHDKeySchema, 289 RestoreScanReachedDepth(nHDKeySchema,
240 nHDReceiveNext, 290 nHDReceiveNext,
241 nHDChangeNext, 291 nHDChangeNext,
242 nHDNext, 292 fRestoreBIP44 ? nLegacyScanNext : nHDNext,
243 nStopDepth)) 293 nStopDepth))
244 break; 294 break;
245 } 295 }
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 04d3fb7..007b8b5 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -7,7 +7,9 @@
7#ifndef BITFLASH_WALLETCMD_H 7#ifndef BITFLASH_WALLETCMD_H
8#define BITFLASH_WALLETCMD_H 8#define BITFLASH_WALLETCMD_H
9 9
10#include <map>
10#include <string> 11#include <string>
12#include <vector>
11 13
12// Create a phrase for a wallet that does not have one, install the seed, and 14// Create a phrase for a wallet that does not have one, install the seed, and
13// show the words once. Refuses if a phrase already exists. Returns 0 on success. 15// show the words once. Refuses if a phrase already exists. Returns 0 on success.
@@ -41,6 +43,13 @@ bool RestoreScanReachedDepth(int nSchema,
41 unsigned int nLegacyNext, 43 unsigned int nLegacyNext,
42 int nStopDepth); 44 int nStopDepth);
43 45
46// Given the pubkeys derived while scanning a compatibility branch, return the
47// next index after the highest one that actually appears in wallet
48// transactions. The scan depth can be much larger than this; hdnext must record
49// use, not how far a restore looked ahead.
50unsigned int WalletLastUsedPubKeyIndexNext(
51 const std::map<unsigned int, std::vector<unsigned char> >& mapPubKeysByIndex);
52
44// Take the next address from the key pool and print it. With a recovery phrase 53// Take the next address from the key pool and print it. With a recovery phrase
45// installed the address is derived, so the phrase can bring back whatever is 54// installed the address is derived, so the phrase can bring back whatever is
46// paid to it. 55// paid to it.