wallet: harden HD seed/chain persistence and record seed provenance
Autoshield sends mined coinbase to a seed-derived z-address, so the records that decide how the seed derives keys become fund-safety critical. Three of them were not treated that way. InstallHDSeed wrote the seed record before the chain record, non-transactionally. A crash between the two left a wallet holding a seed with no hdchain: on the next load hdChain silently reverts to defaults, clearing fMnemonicSeed -- which switches the derivation input from the expanded BIP39 seed to the raw entropy -- and resetting saplingAccountCounter. Write the chain first; the opposite torn state is harmless and self-heals, because HaveHDSeed() is then false and init installs again. The hdchain record was read as a bare deserialise inside a catch-all with strErr never set, and it is not a key type, so a corrupt record was downgraded to a non-critical error and the node booted into the wrong key tree. Report it, track whether it was read, and refuse to load a wallet that holds a seed but no readable hdchain. Also preserve hdchain through a keys-only salvage, which would otherwise drop it and produce exactly the state we now refuse. GenerateNewSeed silently fell back to a random seed when BIP39 generation failed, producing a wallet that looks mnemonic-capable but whose words can never be exported and which no seed phrase can restore. A user who asked for -usemnemonic now gets that or a hard failure. Finally, record how the seed came to exist -- created on an empty wallet, restored from -mnemonic/-hdseed, retrofitted onto a pre-existing seedless wallet, or predating this record. A retrofitted seed is in no backup the user already holds, so a feature that moves funds into addresses only that seed can re-derive must not enable itself there by default. Nothing consumes this yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2307,6 +2307,23 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
|
||||
if (!pwalletMain->HaveHDSeed())
|
||||
{
|
||||
// Does this wallet predate the seed we are about to install? If so,
|
||||
// that seed cannot appear in any backup the user already holds.
|
||||
// Checked BEFORE installing, and before the restore path pre-derives
|
||||
// its gap of keys.
|
||||
bool fWalletHadContent = false;
|
||||
{
|
||||
LOCK(pwalletMain->cs_wallet);
|
||||
std::set<CKeyID> setExistingKeys;
|
||||
pwalletMain->GetKeys(setExistingKeys); // keystore.h:60 / crypter.h:212
|
||||
std::set<libzcash::SaplingPaymentAddress> setExistingZAddrs;
|
||||
pwalletMain->GetSaplingPaymentAddresses(setExistingZAddrs); // keystore.h:226-238
|
||||
fWalletHadContent = !setExistingKeys.empty() ||
|
||||
!setExistingZAddrs.empty() ||
|
||||
!pwalletMain->mapWallet.empty() || // wallet.h:1041
|
||||
pwalletMain->IsCrypted(); // crypter.h:174
|
||||
}
|
||||
|
||||
std::string mnemonic = GetArg("-mnemonic", "");
|
||||
std::string hdSeedHex = GetArg("-hdseed", "");
|
||||
bool restoring = false;
|
||||
@@ -2338,6 +2355,19 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
pwalletMain->GenerateNewSeed();
|
||||
}
|
||||
|
||||
pwalletMain->SetHDSeedOrigin(restoring
|
||||
? CWallet::HDSEED_ORIGIN_RESTORED
|
||||
: (fWalletHadContent ? CWallet::HDSEED_ORIGIN_RETROFIT
|
||||
: CWallet::HDSEED_ORIGIN_CREATED));
|
||||
if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_RETROFIT)
|
||||
{
|
||||
LogPrintf("%s: WARNING: generated a new HD seed for a wallet that already held keys or "
|
||||
"transactions. This seed is in NO backup you made before now.\n", __func__);
|
||||
InitWarning(_("A new HD seed was generated for this pre-existing wallet. Any backup you "
|
||||
"made before now does not contain it: back the wallet up again "
|
||||
"(z_exportwallet) before receiving funds to newly derived addresses."));
|
||||
}
|
||||
|
||||
if (restoring)
|
||||
{
|
||||
// Pre-derive keys (birthday = genesis) so the startup rescan finds
|
||||
@@ -2356,6 +2386,15 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
|
||||
LogPrintf("%s: pre-derived %d transparent and %d sapling keys for restore rescan\n", __func__, (int)tGap, (int)zGap);
|
||||
}
|
||||
}
|
||||
else if (pwalletMain->hdSeedOrigin == CWallet::HDSEED_ORIGIN_UNRECORDED)
|
||||
{
|
||||
// The seed was installed by a build that predates this record, so
|
||||
// we cannot tell whether it was minted onto a pre-existing wallet
|
||||
// (and is therefore absent from the user's older backups). Assume
|
||||
// the worst; the user can still opt in explicitly.
|
||||
pwalletMain->SetHDSeedOrigin(CWallet::HDSEED_ORIGIN_UNKNOWN);
|
||||
LogPrintf("%s: HD seed predates seed-provenance recording; recorded origin as unknown\n", __func__);
|
||||
}
|
||||
|
||||
//Set Sapling Consolidation
|
||||
pwalletMain->fSaplingConsolidationEnabled = GetBoolArg("-consolidation", false);
|
||||
|
||||
Reference in New Issue
Block a user