wallet: plumb the mnemonic entropy through CWallet
Wires the key store secret to the database records: load and store paths on CWallet, the two ReadKeyValue arms, and the export path. GetMnemonicPhrase now prefers the entropy record and verifies it before printing: a phrase is only returned if expanding it reproduces the seed derivation actually uses. It falls back to the existing fMnemonicSeed path, so wallets that store the entropy AS the seed keep working unchanged. IsMnemonicSeed() now means "a phrase is available" rather than "the seed is the entropy", which is what every caller actually wants. Still a no-op on every existing wallet: nothing creates an entropy record yet, so GetMnemonicEntropy returns false and the old code path is taken. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2587,6 +2587,72 @@ bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigne
|
||||
{
|
||||
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
|
||||
}
|
||||
bool CWallet::SetMnemonicEntropy(const RawHDSeed& entropy)
|
||||
{
|
||||
if (!CCryptoKeyStore::SetMnemonicEntropy(entropy)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fFileBacked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
if (!IsCrypted()) {
|
||||
// Keyed by fingerprint exactly as "hdseed" is, so ReadKeyValue can
|
||||
// integrity-check it and EraseMnemonicEntropy can find it later.
|
||||
return CWalletDB(strWalletFile).WriteMnemonicEntropy(
|
||||
MnemonicEntropyFingerprint(entropy), entropy);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CWallet::SetCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
|
||||
{
|
||||
if (!CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fFileBacked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
// Same write-then-erase discipline as SetCryptedHDSeed: CDB::Rewrite at
|
||||
// the end of EncryptWallet copies every surviving record, so a leftover
|
||||
// plaintext "mnementropy" would keep the seed phrase recoverable from an
|
||||
// encrypted wallet.dat. The erase is best-effort for the same reason: a
|
||||
// hard failure would propagate into EncryptKeys -> assert(false).
|
||||
if (pwalletdbEncryption) {
|
||||
if (!pwalletdbEncryption->WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
|
||||
return false;
|
||||
if (!pwalletdbEncryption->EraseMnemonicEntropy(entropyFp))
|
||||
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
|
||||
return true;
|
||||
} else {
|
||||
CWalletDB walletdb(strWalletFile);
|
||||
if (!walletdb.WriteCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
|
||||
return false;
|
||||
if (!walletdb.EraseMnemonicEntropy(entropyFp))
|
||||
LogPrintf("%s: WARNING: could not erase the plaintext mnementropy record\n", __func__);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CWallet::LoadMnemonicEntropy(const RawHDSeed& entropy)
|
||||
{
|
||||
return CBasicKeyStore::SetMnemonicEntropy(entropy);
|
||||
}
|
||||
|
||||
bool CWallet::LoadCryptedMnemonicEntropy(const uint256& entropyFp, const std::vector<unsigned char>& vchCryptedSecret)
|
||||
{
|
||||
return CCryptoKeyStore::SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret);
|
||||
}
|
||||
|
||||
bool CWallet::InstallHDSeed(const HDSeed& seed, bool fMnemonic, int64_t nCreateTime)
|
||||
{
|
||||
@@ -2675,6 +2741,36 @@ bool CWallet::GetHDSeedForDerivation(HDSeed& seedOut) const
|
||||
|
||||
bool CWallet::GetMnemonicPhrase(std::string& phraseOut) const
|
||||
{
|
||||
// Preferred form: the HD seed is the EXPANDED 64-byte BIP39 seed and the
|
||||
// entropy sits in its own record.
|
||||
RawHDSeed entropy;
|
||||
if (GetMnemonicEntropy(entropy)) { // false on an encrypted+locked wallet
|
||||
// NEVER hand out a phrase that does not restore THIS wallet. Prove the
|
||||
// entropy expands to the exact bytes derivation consumes; if it does
|
||||
// not (a torn install, a wallet.dat edited by hand, an entropy record
|
||||
// paired with a different seed), refuse rather than print a phrase that
|
||||
// silently restores someone else's key tree. Costs one PBKDF2 on a
|
||||
// user-initiated RPC.
|
||||
RawHDSeed seed64;
|
||||
if (!Bip39SeedFromEntropy(entropy, seed64))
|
||||
return false;
|
||||
|
||||
HDSeed derivationSeed;
|
||||
if (!GetHDSeedForDerivation(derivationSeed))
|
||||
return false;
|
||||
|
||||
if (derivationSeed.RawSeed() != seed64) {
|
||||
LogPrintf("%s: refusing to export a seed phrase: the stored mnemonic entropy does not "
|
||||
"expand to this wallet's HD seed\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
return EntropyToMnemonic(entropy, phraseOut);
|
||||
}
|
||||
|
||||
// Legacy form (earlier builds of this branch): the stored HD seed IS the
|
||||
// 32-byte BIP39 entropy, expanded on every derivation. Consistent by
|
||||
// construction, so no cross-check is possible or needed.
|
||||
if (!hdChain.fMnemonicSeed)
|
||||
return false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user