upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-01 21:48:03 -0300
committerGitHub <noreply@github.com>2026-08-01 21:48:03 -0300
commitd54a07170808b921c79e25c2a0a8f6b9c447d4ea (patch)
treebe83d3b40c1e466b1ecab1e3f5375b6a9b23c755 /src/main.cpp
parent5a5478669c487963e99491112abc065c59973966 (diff)
wallet: reserve index 0 before deriving it, and name the address the phrase does not cover (#104)
Follow-up to #102. Reserves derivation index 0 before deriving it, so a failure between installing the seed and writing the default key cannot leave the pool free to hand that index out as an ordinary key. Renames the address that stops being the default to 'Your Address (created before the recovery phrase)', which was otherwise left under the same name as the new one. Also documents why the key-pool clear precedes the seed write, correcting a review comment of mine that asked for the reverse.
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp42
1 files changed, 34 insertions, 8 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 2c4e346..9082bd5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -254,21 +254,46 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet)
254 254
255 CRITICAL_BLOCK(cs_keyPool) 255 CRITICAL_BLOCK(cs_keyPool)
256 { 256 {
257 // Old private keys stay in wallet.dat, but old unused pool records must 257 // The pool is cleared before the seed is written, and the order is the
258 // not survive the seed. Otherwise the next address can still be random 258 // safe one of the two. Old private keys stay in wallet.dat either way;
259 // while the UI says a recovery phrase exists. 259 // what is at stake is which half-finished state a failure can leave.
260 //
261 // clear, then write -- a failed write leaves the old seed and an
262 // empty pool. TopUpKeyPool refills it, and nothing was promised to
263 // anybody.
264 //
265 // write, then clear -- a failed clear leaves a seed installed and the
266 // old random pool intact. The user has just been shown twelve words
267 // and the wallet goes on handing out addresses those words cannot
268 // reproduce. That is the bug this function exists to prevent.
269 //
270 // An earlier comment here said the seed was written first so a failure
271 // left the wallet untouched. That was true of the seed alone and stops
272 // being true once the pool has to move with it.
260 if (!ClearKeyPoolRecords(strErrorRet)) 273 if (!ClearKeyPoolRecords(strErrorRet))
261 return false; 274 return false;
262 275
263 if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) || 276 if (!CWalletDB().WriteHDMaster(master.privateKey, master.chainCode) ||
264 !CWalletDB().WriteHDNext(0)) 277 !CWalletDB().WriteHDNext(1))
265 { 278 {
266 strErrorRet = "could not write the seed to wallet.dat"; 279 strErrorRet = "could not write the seed to wallet.dat";
267 return false; 280 return false;
268 } 281 }
269 vchHDMaster = master.privateKey; 282 vchHDMaster = master.privateKey;
270 vchHDChainCode = master.chainCode; 283 vchHDChainCode = master.chainCode;
271 nHDNext = 0; 284
285 // Index 0 is spoken for before it is derived, so a failure below cannot
286 // leave the pool free to hand it out as an ordinary key. The cost of
287 // the counter being ahead of the derivation is one unused index; the
288 // cost of the reverse is the default address and a pool key sharing a
289 // derivation path.
290 nHDNext = 1;
291
292 // Remember which address stops being the default, so it can be named
293 // for what it is. Two entries reading "Your Address" -- one covered by
294 // the phrase and one not -- is the wrong thing to find at the moment a
295 // person has just written twelve words down.
296 vector<unsigned char> vchOldDefault = keyUser.GetPubKey();
272 297
273 CKey key; 298 CKey key;
274 if (!DeriveHDKey(0, key, strErrorRet)) 299 if (!DeriveHDKey(0, key, strErrorRet))
@@ -280,14 +305,15 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet)
280 } 305 }
281 vector<unsigned char> vchPubKey = key.GetPubKey(); 306 vector<unsigned char> vchPubKey = key.GetPubKey();
282 if (!CWalletDB().WriteDefaultKey(vchPubKey) || 307 if (!CWalletDB().WriteDefaultKey(vchPubKey) ||
283 !SetAddressBookName(PubKeyToAddress(vchPubKey), "Your Address") || 308 !SetAddressBookName(PubKeyToAddress(vchPubKey), "Your Address"))
284 !CWalletDB().WriteHDNext(1))
285 { 309 {
286 strErrorRet = "could not set the default derived receiving key"; 310 strErrorRet = "could not set the default derived receiving key";
287 return false; 311 return false;
288 } 312 }
313 if (!vchOldDefault.empty() && vchOldDefault != vchPubKey)
314 SetAddressBookName(PubKeyToAddress(vchOldDefault),
315 "Your Address (created before the recovery phrase)");
289 keyUser = key; 316 keyUser = key;
290 nHDNext = 1;
291 } 317 }
292 return true; 318 return true;
293} 319}