fix(wallets): detect seed-phrase wallets offline via hdchain fMnemonicSeed
The wallet switcher couldn't tell a BIP39 seed-phrase wallet from a legacy / raw-entropy HD wallet without loading it, so non-active rows fell back to bare HD-record presence — which mislabeled every HD wallet as "Seed phrase". The distinction is actually on disk: the daemon serializes CHDChain with an fMnemonicSeed bool (VERSION_HD_MNEMONIC=3, byte offset 52), and the hdchain record stays plaintext even in an encrypted wallet. Read it directly: - wallet_file_probe.h: hdChainMnemonicFlag() decodes the flag from the hdchain value (1 mnemonic / 2 no-phrase / 0 undecidable); WalletBtreeStats.mnemonicSeed surfaces it from the tier-2 btree walk. - wallets_dialog.h: the badge prefers runtime z_exportmnemonic for the active wallet, then the on-disk flag, then HD-record presence — so every row is classified correctly (or honestly shows "?" when the flag can't be read, e.g. the tier-1 byte-scan fallback). - tests: decode-level cases (v3 set/clear, v1/v2, truncated, garbage version) plus an end-to-end btree walk asserting mnemonicSeed. Offset math + badge logic adversarially verified against the daemon source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -288,16 +288,23 @@ public:
|
||||
// encryption — so absence of a lock never falsely reads as "unencrypted" on a huge wallet.
|
||||
const ProbeResult pres = probeAt(i); // from the frame-consistent snapshot above
|
||||
const bool bLock = pres.probed && pres.encrypted;
|
||||
// Seed-phrase vs legacy: the offline probe only sees HD records (hdseed/hdchain), which
|
||||
// are present in BOTH mnemonic and legacy-HD wallets — so it can't tell them apart. For
|
||||
// the ACTIVE wallet the app knows the truth (z_exportmnemonic → activeWalletSeedBadge);
|
||||
// prefer that and fall back to the probe only for non-active or not-yet-decided wallets.
|
||||
// Seed-phrase vs legacy. Runtime status (z_exportmnemonic → activeWalletSeedBadge) is
|
||||
// authoritative for the ACTIVE wallet; otherwise the offline probe reads the hdchain
|
||||
// record's fMnemonicSeed flag directly (pres.mnemonic: 1 = BIP39 seed phrase, 2 = HD/legacy
|
||||
// with no phrase, 0 = couldn't tell) — which, unlike bare HD-record presence, actually
|
||||
// distinguishes the two. seed uses the same 1/2/0 encoding.
|
||||
const int activeBadge = rowActive[i] ? app->activeWalletSeedBadge() : 0;
|
||||
const bool bSeed = (activeBadge == 1) || (activeBadge == 0 && pres.probed && pres.hdSeed);
|
||||
const bool bLegacy = (activeBadge == 2) ||
|
||||
(activeBadge == 0 && pres.probed && pres.complete && !pres.hdSeed);
|
||||
// No "unknown" seed badge once the runtime status is authoritative for the active wallet.
|
||||
const bool bUnknown = (activeBadge == 0) && pres.probed && !pres.complete && !pres.encrypted;
|
||||
int seed = activeBadge;
|
||||
if (seed == 0 && pres.probed) {
|
||||
if (pres.mnemonic != 0) seed = pres.mnemonic; // read the flag off disk
|
||||
else if (pres.complete && !pres.hdSeed) seed = 2; // no HD records at all → no phrase
|
||||
}
|
||||
const bool bSeed = (seed == 1);
|
||||
const bool bLegacy = (seed == 2);
|
||||
// Undecided: probed but neither the runtime status nor the on-disk flag could classify it
|
||||
// (incomplete scan, or an unreadable hdchain value) — show a question mark rather than
|
||||
// mislabel. Suppressed for encrypted rows, which still carry a definitive Lock badge.
|
||||
const bool bUnknown = (seed == 0) && pres.probed && !pres.encrypted;
|
||||
struct Badge { const char* glyph; ImU32 col; const char* label; const char* tip; };
|
||||
Badge bl[4]; int nb = 0;
|
||||
if (bSeed) bl[nb++] = { ICON_MD_ECO, WithAlpha(Success(), 235), TR("wallets_badge_seed_short"), TR("wallets_badge_seed") };
|
||||
@@ -549,6 +556,7 @@ private:
|
||||
int keyCount = 0; // transparent + shielded spendable keys (≈ addresses, incl. change)
|
||||
int txCount = 0; // wallet transaction records
|
||||
long long createdEpoch = 0; // wallet birthday (earliest keymeta nCreateTime); 0 = unknown
|
||||
int mnemonic = 0; // hdchain fMnemonicSeed flag: 0 unknown, 1 seed-phrase, 2 no phrase
|
||||
};
|
||||
struct ProbeBatch {
|
||||
std::mutex mtx;
|
||||
@@ -775,6 +783,7 @@ private:
|
||||
const auto bt = util::parseWalletBtree(t.first, std::min(budget, kPerFile));
|
||||
if (bt.parsed && bt.complete) {
|
||||
res = ProbeResult{ true, true, bt.encrypted, bt.hdSeed, true, bt.addresses(), bt.txCount, bt.createdEpoch };
|
||||
res.mnemonic = bt.mnemonicSeed; // read straight off the hdchain record
|
||||
budget -= std::min(budget, bt.bytesRead);
|
||||
} else {
|
||||
const auto pr = util::probeWalletFile(t.first, std::min(budget, kPerFile));
|
||||
|
||||
Reference in New Issue
Block a user