upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/walletcmd.cpp
diff options
context:
space:
mode:
authormtg0001 <106211915+mtg0001@users.noreply.github.com>2026-08-03 15:11:55 -0300
committerGitHub <noreply@github.com>2026-08-03 15:11:55 -0300
commitaba252206bf39ac81e6627354062c28fae05b0aa (patch)
tree751691f0baaf925c54a2c03f9f02ca87fb29956f /src/walletcmd.cpp
parent1aee805b232a33484e3db1dc8ea4bfb3f585212e (diff)
Activate BIP44 wallet derivation (#124)
* Add BIP32 path derivation foundation * Add HD wallet schema metadata * Add provisional BIP44 coin type metadata * Activate BIP44 wallet derivation * wallet: scan BIP44 restore depth per branch * wallet: widen default BIP44 restore lookahead --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Diffstat (limited to 'src/walletcmd.cpp')
-rw-r--r--src/walletcmd.cpp117
1 files changed, 96 insertions, 21 deletions
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 41b4ed2..b449df9 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -25,16 +25,34 @@
25// would restore a wallet that looks empty. 25// would restore a wallet that looks empty.
26static const int RESTORE_BATCH = 100; 26static const int RESTORE_BATCH = 100;
27static const int RESTORE_MAX = 10000; 27static const int RESTORE_MAX = 10000;
28// The wallet may have a full unused key pool in front of a change address
29// created while spending pre-phrase coins. Looking only one empty batch ahead
30// can stop just before that change output.
31static const int RESTORE_MIN_SCAN = KEYPOOL_SIZE + RESTORE_BATCH;
32
33// Quiet batches required before giving up. Three, because the wallet's own 28// Quiet batches required before giving up. Three, because the wallet's own
34// bookkeeping can leave a gap of two hundred used-nothing indices between one 29// bookkeeping can leave a gap of two hundred used-nothing indices between one
35// used address and the next -- see the note at the stop condition. One was not 30// used address and the next -- see the note at the stop condition. One was not
36// enough and cost a real balance in testing. 31// enough and cost a real balance in testing.
37static const int RESTORE_QUIET_BATCHES = 3; 32static const int RESTORE_QUIET_BATCHES = 3;
33// The wallet may have restored once, scanned a few quiet batches, and then
34// spent after that. Change starts from wherever that restore stopped, so the
35// next real change output can be just beyond the old quiet window. The default
36// restore has to look far enough for that ordinary "restore, then spend, then
37// restore again" path; explicit -restoredepth can still go deeper.
38static const int RESTORE_MIN_SCAN =
39 KEYPOOL_SIZE + (RESTORE_QUIET_BATCHES + 1) * RESTORE_BATCH;
40
41bool RestoreScanReachedDepth(int nSchema,
42 unsigned int nReceiveNext,
43 unsigned int nChangeNext,
44 unsigned int nLegacyNext,
45 int nStopDepth)
46{
47 if (nStopDepth <= 0)
48 return true;
49 unsigned int nDepth = (unsigned int)nStopDepth;
50 if (nSchema == HD_SCHEMA_BIP44)
51 return nReceiveNext >= nDepth &&
52 nChangeNext >= nDepth &&
53 nLegacyNext >= nDepth;
54 return nLegacyNext >= nDepth;
55}
38 56
39int CmdNewPhrase() 57int CmdNewPhrase()
40{ 58{
@@ -114,7 +132,9 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
114 // Derive forward in batches, scanning after each, until a whole batch turns 132 // Derive forward in batches, scanning after each, until a whole batch turns
115 // up nothing. Every derived key is written to the wallet before the scan, 133 // up nothing. Every derived key is written to the wallet before the scan,
116 // because the scan asks the wallet what belongs to it. 134 // because the scan asks the wallet what belongs to it.
117 int nTotalDerived = (int)nHDNext; 135 int nTotalDerived = nHDKeySchema == HD_SCHEMA_BIP44
136 ? (int)(nHDReceiveNext + nHDChangeNext)
137 : (int)nHDNext;
118 int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN); 138 int nStopDepth = max(nMinDepth, RESTORE_MIN_SCAN);
119 int nQuietBatches = 0; 139 int nQuietBatches = 0;
120 while (nTotalDerived < RESTORE_MAX) 140 while (nTotalDerived < RESTORE_MAX)
@@ -131,17 +151,61 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
131 nWalletBefore = mapWallet.size(); 151 nWalletBefore = mapWallet.size();
132 152
133 std::string strDeriveError; 153 std::string strDeriveError;
134 for (int i = 0; i < RESTORE_BATCH; i++) 154 if (nHDKeySchema == HD_SCHEMA_BIP44)
155 {
156 for (int i = 0; i < RESTORE_BATCH; i++)
157 {
158 CKey key;
159 if (!DeriveHDKey(nHDReceiveNext, key, strDeriveError))
160 break;
161 if (!AddKey(key))
162 break;
163 nHDReceiveNext++;
164 nTotalDerived++;
165 }
166 CWalletDB().WriteHDReceiveNext(nHDReceiveNext);
167
168 for (int i = 0; i < RESTORE_BATCH; i++)
169 {
170 CKey key;
171 if (!DeriveHDChangeKey(nHDChangeNext, key, strDeriveError))
172 break;
173 if (!AddKey(key))
174 break;
175 nHDChangeNext++;
176 nTotalDerived++;
177 }
178 CWalletDB().WriteHDChangeNext(nHDChangeNext);
179
180 int nSavedSchema = nHDKeySchema;
181 nHDKeySchema = HD_SCHEMA_LEGACY;
182 for (int i = 0; i < RESTORE_BATCH; i++)
183 {
184 CKey key;
185 if (!DeriveHDKey(nHDNext, key, strDeriveError))
186 break;
187 if (!AddKey(key))
188 break;
189 nHDNext++;
190 nTotalDerived++;
191 }
192 nHDKeySchema = nSavedSchema;
193 CWalletDB().WriteHDNext(nHDNext);
194 }
195 else
135 { 196 {
136 CKey key; 197 for (int i = 0; i < RESTORE_BATCH; i++)
137 if (!DeriveHDKey(nHDNext, key, strDeriveError)) 198 {
138 break; 199 CKey key;
139 if (!AddKey(key)) 200 if (!DeriveHDKey(nHDNext, key, strDeriveError))
140 break; 201 break;
141 nHDNext++; 202 if (!AddKey(key))
142 nTotalDerived++; 203 break;
204 nHDNext++;
205 nTotalDerived++;
206 }
207 CWalletDB().WriteHDNext(nHDNext);
143 } 208 }
144 CWalletDB().WriteHDNext(nHDNext);
145 209
146 ScanForWalletTransactions(pindexGenesisBlock); 210 ScanForWalletTransactions(pindexGenesisBlock);
147 211
@@ -157,11 +221,11 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
157 } 221 }
158 222
159 // Stopping needs more than one quiet batch, because this wallet digs 223 // Stopping needs more than one quiet batch, because this wallet digs
160 // gaps in its own derivation. A restore leaves nHDNext at the depth it 224 // gaps in its own derivation. A restore leaves the receive counter at
161 // scanned, the key pool then derives KEYPOOL_SIZE more, and change 225 // the depth it scanned, the key pool then derives KEYPOOL_SIZE more,
162 // takes the index after that -- so the address used *after* a restore 226 // and a later spend can use change after that -- so the address used
163 // can sit two hundred indices past the last one used before it, with 227 // *after* a restore can sit two hundred indices past the last one used
164 // nothing in between. 228 // before it, with nothing in between.
165 // 229 //
166 // Measured, on a real wallet with real coin: restore, spend once, and 230 // Measured, on a real wallet with real coin: restore, spend once, and
167 // the coins land at indices 201 and 302. Restoring again with the old 231 // the coins land at indices 201 and 302. Restoring again with the old
@@ -171,7 +235,12 @@ bool RestoreFromPhrase(const std::string& strMnemonic,
171 nQuietBatches++; 235 nQuietBatches++;
172 else 236 else
173 nQuietBatches = 0; 237 nQuietBatches = 0;
174 if (nQuietBatches >= RESTORE_QUIET_BATCHES && nTotalDerived >= nStopDepth) 238 if (nQuietBatches >= RESTORE_QUIET_BATCHES &&
239 RestoreScanReachedDepth(nHDKeySchema,
240 nHDReceiveNext,
241 nHDChangeNext,
242 nHDNext,
243 nStopDepth))
175 break; 244 break;
176 } 245 }
177 246
@@ -283,7 +352,13 @@ int CmdRecoveryAudit()
283 printf("Wallet recovery audit\n"); 352 printf("Wallet recovery audit\n");
284 printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); 353 printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed");
285 if (audit.fHaveSeed) 354 if (audit.fHaveSeed)
355 {
356 printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str());
357 printf(" BIP44 coin type: %u (provisional BITFLASH)\n", audit.nCoinType);
286 printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown); 358 printf(" derived keys known to this wallet: %u\n", audit.nDerivedKnown);
359 printf(" receive/change counters: %u/%u\n",
360 audit.nReceiveNext, audit.nChangeNext);
361 }
287 if (!audit.fDeriveComplete) 362 if (!audit.fDeriveComplete)
288 printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); 363 printf(" derivation warning: %s\n", audit.strDeriveError.c_str());
289 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); 364 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str());