Add BIP39 seed phrases (SilentDragonXLite-compatible) and HD transparent keys

Derive transparent (t-addr) keys from the HD seed and add BIP39 mnemonic seed
phrases that are byte-for-byte compatible with SilentDragonXLite, so the same
24 words recover the same shielded and transparent addresses in either wallet.

HD transparent keys:
- Derive t-keys from the seed at m/44'/coin'/0'/0/i (were random CKeys).
- CHDChain gains a version-gated transparent counter; existing wallets load
  unchanged. GenerateNewKey routes through DeriveNewChildKey when enabled
  (-hdtransparent, default on).
- Restore from a seed hex via -hdseed with gap-limit pre-derivation; birthday
  pinned to genesis so the rescan is not clipped.

BIP39 seed phrases:
- Wire the vendored trezor BIP39 lib (src/crypto/bip39) into the build, fix its
  BIP39_WORDS guard, and disable the insecure mnemonic cache.
- Match SDXLite exactly: English wordlist, empty passphrase, PBKDF2 64-byte
  seed, coin type 141, ZIP-32 m/32'/141'/i' and BIP44 m/44'/141'/0'/0/i. Store
  the 32-byte entropy and expand to the 64-byte seed on demand.
- Restore via -mnemonic, create via -usemnemonic, reveal via z_exportmnemonic.

Verified by gtests including a known-answer BIP39 seed vector and z/t address
derivation checks (src/gtest/test_hdtransparent.cpp, test_mnemonic_compat.cpp).
Docs in doc/hd-transparent-keys.md and doc/seed-phrase.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-06 01:57:18 -05:00
parent 84aefb5475
commit 4caf2fc68f
23 changed files with 1044 additions and 22 deletions

View File

@@ -742,7 +742,9 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
{
HDSeed hdSeed;
pwalletMain->GetHDSeed(hdSeed);
// Dump the 64-byte derivation seed (for mnemonic wallets this is the
// expanded BIP39 seed), so re-importing the hex reproduces the same keys.
pwalletMain->GetHDSeedForDerivation(hdSeed);
auto rawSeed = hdSeed.RawSeed();
file << strprintf("# HDSeed=%s fingerprint=%s", HexStr(rawSeed.begin(), rawSeed.end()), hdSeed.Fingerprint().GetHex());
file << "\n";
@@ -1026,6 +1028,50 @@ UniValue z_exportkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
return EncodeSpendingKey(sk.get());
}
UniValue z_exportmnemonic(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() != 0)
throw runtime_error(
"z_exportmnemonic\n"
"\nReveal the wallet's BIP39 seed phrase (24 words).\n"
"The phrase is byte-compatible with SilentDragonXLite: the same words\n"
"restore the same transparent and shielded addresses in either wallet.\n"
"Only works for wallets created or restored from a mnemonic (see the\n"
"-mnemonic and -usemnemonic options). Requires the wallet be unlocked.\n"
"\nResult:\n"
"{\n"
" \"mnemonic\" : \"word1 ... word24\", (string) the BIP39 seed phrase\n"
" \"seedfp\" : \"hex\" (string) the HD seed fingerprint\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_exportmnemonic", "")
+ HelpExampleRpc("z_exportmnemonic", "")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
if (!pwalletMain->IsMnemonicSeed()) {
throw JSONRPCError(RPC_WALLET_ERROR,
"This wallet's seed was not derived from a mnemonic, so no seed phrase is available. "
"Use z_exportwallet to back up the raw HD seed instead.");
}
std::string phrase;
if (!pwalletMain->GetMnemonicPhrase(phrase)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not retrieve the seed phrase (is the wallet unlocked?)");
}
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("mnemonic", phrase));
ret.push_back(Pair("seedfp", pwalletMain->GetHDChain().seedFp.GetHex()));
return ret;
}
UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))