wallet: add BIP39 passphrase support throughout HD lifecycle
hdPassphrase was hardcoded to empty string in DeriveHDKey, meaning users
who set a BIP39 passphrase during seed creation would derive different
addresses after restoration. This adds proper passphrase storage,
encryption, and decryption alongside the existing mnemonic handling:
- wallet.h: hdPassphrase + vchCryptedHDPassphrase + hdPassphraseIV fields
- wallet.cpp: Lock/Unlock/EncryptWallet/SetHDSeed all handle passphrase
with the same encrypt/decrypt lifecycle as the mnemonic
- DeriveHDKey now passes hdPassphrase to DeriveTriangles (not hardcoded )
- walletdb.h: WriteHDPassphrase/WriteHDCryptedPassphrase/EraseHDPassphrase
- walletdb.cpp: ReadKeyValue handles hdpassphrase/hdcpassphrase records
- rpcwallet.cpp: hdnew/hdshow show passphrase_used + warnings
Also: i2p.cpp hardens I2P private key file permissions to owner-only.
From Claude's uncommitted work on E:\repos\triangles (SAMI-PC). The rest
of Claude's modernization (Boost removal, RPC rewrite, RocksDB default,
SQLite wallet) was already committed to master in bfdb399 and follow-ups.
This commit is contained in:
+12
@@ -223,6 +223,18 @@ bool CI2PSession::LoadOrCreateDestination(std::string& strPrivKeyRet)
|
|||||||
if (out.is_open()) {
|
if (out.is_open()) {
|
||||||
out << priv << std::endl;
|
out << priv << std::endl;
|
||||||
out.close();
|
out.close();
|
||||||
|
// The I2P destination private key identifies this node on
|
||||||
|
// the I2P network: owner-only permissions, like Tor's
|
||||||
|
// hidden-service secret key. (No-op semantics differ on
|
||||||
|
// Windows ACLs; harmless there.)
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::permissions(keyPath,
|
||||||
|
std::filesystem::perms::owner_read |
|
||||||
|
std::filesystem::perms::owner_write,
|
||||||
|
std::filesystem::perm_options::replace, ec);
|
||||||
|
if (ec)
|
||||||
|
printf("I2P: WARNING could not restrict permissions on %s: %s\n",
|
||||||
|
keyPath.string().c_str(), ec.message().c_str());
|
||||||
printf("I2P: generated and saved new persistent destination\n");
|
printf("I2P: generated and saved new persistent destination\n");
|
||||||
ok = true;
|
ok = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+9
-2
@@ -1919,7 +1919,10 @@ Value hdnew(const Array& params, bool fHelp)
|
|||||||
Object obj;
|
Object obj;
|
||||||
obj.push_back(Pair("mnemonic", mnemonic));
|
obj.push_back(Pair("mnemonic", mnemonic));
|
||||||
obj.push_back(Pair("words", 24));
|
obj.push_back(Pair("words", 24));
|
||||||
obj.push_back(Pair("warning", "Write these 24 words down and keep them secret and offline. Anyone with them can spend your coins."));
|
obj.push_back(Pair("passphrase_used", !passphrase.empty()));
|
||||||
|
obj.push_back(Pair("warning", passphrase.empty()
|
||||||
|
? "Write these 24 words down and keep them secret and offline. Anyone with them can spend your coins."
|
||||||
|
: "Write these 24 words down and keep them secret and offline. Anyone with them can spend your coins. You ALSO set a BIP39 passphrase: the words alone will NOT restore this wallet — back up the passphrase separately."));
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1958,6 +1961,10 @@ Value hdshow(const Array& params, bool fHelp)
|
|||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet has no HD seed (use 'hdnew' to create one).");
|
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet has no HD seed (use 'hdnew' to create one).");
|
||||||
Object obj;
|
Object obj;
|
||||||
obj.push_back(Pair("mnemonic", mnemonic));
|
obj.push_back(Pair("mnemonic", mnemonic));
|
||||||
obj.push_back(Pair("warning", "Keep these words secret and offline."));
|
obj.push_back(Pair("passphrase_used", !pwalletMain->hdPassphrase.empty()));
|
||||||
|
if (!pwalletMain->hdPassphrase.empty())
|
||||||
|
obj.push_back(Pair("warning", "Keep these words secret and offline. A BIP39 passphrase is ALSO set: the words alone will NOT restore this wallet — back up the passphrase separately."));
|
||||||
|
else
|
||||||
|
obj.push_back(Pair("warning", "Keep these words secret and offline."));
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-3
@@ -221,8 +221,10 @@ bool CWallet::Lock()
|
|||||||
if (fDebug)
|
if (fDebug)
|
||||||
printf("Locking wallet.\n");
|
printf("Locking wallet.\n");
|
||||||
|
|
||||||
if (IsCrypted())
|
if (IsCrypted()) {
|
||||||
hdMnemonic.clear(); // keep only the encrypted copy while locked
|
hdMnemonic.clear(); // keep only the encrypted copies while locked
|
||||||
|
hdPassphrase.clear();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
@@ -254,6 +256,11 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase)
|
|||||||
if (DecryptSecret(vMasterKey, vchCryptedHDMnemonic, hdMnemonicIV, sec))
|
if (DecryptSecret(vMasterKey, vchCryptedHDMnemonic, hdMnemonicIV, sec))
|
||||||
hdMnemonic.assign(sec.begin(), sec.end());
|
hdMnemonic.assign(sec.begin(), sec.end());
|
||||||
}
|
}
|
||||||
|
if (fHDEnabled && hdPassphrase.empty() && !vchCryptedHDPassphrase.empty()) {
|
||||||
|
CSecret psec;
|
||||||
|
if (DecryptSecret(vMasterKey, vchCryptedHDPassphrase, hdPassphraseIV, psec))
|
||||||
|
hdPassphrase.assign(psec.begin(), psec.end());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,6 +443,14 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
||||||
dbEnc->WriteHDCryptedMnemonic(iv, cipher);
|
dbEnc->WriteHDCryptedMnemonic(iv, cipher);
|
||||||
}
|
}
|
||||||
|
if (fHDEnabled && !hdPassphrase.empty()) {
|
||||||
|
CSecret psec(hdPassphrase.begin(), hdPassphrase.end());
|
||||||
|
uint256 piv = GetRandHash();
|
||||||
|
std::vector<unsigned char> pcipher;
|
||||||
|
if (!EncryptSecret(vMasterKey, psec, piv, pcipher)) { dbEnc->TxnAbort(); return false; }
|
||||||
|
hdPassphraseIV = piv; vchCryptedHDPassphrase = pcipher;
|
||||||
|
dbEnc->WriteHDCryptedPassphrase(piv, pcipher);
|
||||||
|
}
|
||||||
|
|
||||||
SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
SetMinVersion(WalletFeature::WalletCrypt, dbEnc.get(), true);
|
||||||
|
|
||||||
@@ -2933,8 +2948,11 @@ bool CWallet::DeriveHDKey(int64_t index, CKey& keyOut) const
|
|||||||
{
|
{
|
||||||
if (hdMnemonic.empty())
|
if (hdMnemonic.empty())
|
||||||
return false;
|
return false;
|
||||||
|
// If a BIP39 passphrase ("25th word") was set with the seed, it MUST be
|
||||||
|
// part of every derivation — otherwise restored wallets derive different
|
||||||
|
// addresses than the originals. Empty string = no passphrase (legacy).
|
||||||
unsigned char priv[32];
|
unsigned char priv[32];
|
||||||
if (!hd::DeriveTriangles(hdMnemonic, "", 0, 0, (uint32_t)index, priv))
|
if (!hd::DeriveTriangles(hdMnemonic, hdPassphrase, 0, 0, (uint32_t)index, priv))
|
||||||
return false;
|
return false;
|
||||||
CSecret secret(priv, priv + 32);
|
CSecret secret(priv, priv + 32);
|
||||||
memset(priv, 0, sizeof(priv));
|
memset(priv, 0, sizeof(priv));
|
||||||
@@ -2968,6 +2986,7 @@ bool CWallet::SetHDSeed(const std::string& mnemonicIn, const std::string& passph
|
|||||||
memset(priv, 0, sizeof(priv));
|
memset(priv, 0, sizeof(priv));
|
||||||
|
|
||||||
hdMnemonic = m;
|
hdMnemonic = m;
|
||||||
|
hdPassphrase = passphrase;
|
||||||
fHDEnabled = true;
|
fHDEnabled = true;
|
||||||
nHDChainIndex = 0;
|
nHDChainIndex = 0;
|
||||||
|
|
||||||
@@ -2980,8 +2999,23 @@ bool CWallet::SetHDSeed(const std::string& mnemonicIn, const std::string& passph
|
|||||||
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { strError = "Failed to encrypt seed."; return false; }
|
if (!EncryptSecret(vMasterKey, sec, iv, cipher)) { strError = "Failed to encrypt seed."; return false; }
|
||||||
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher;
|
||||||
wdb.WriteHDCryptedMnemonic(iv, cipher);
|
wdb.WriteHDCryptedMnemonic(iv, cipher);
|
||||||
|
if (!passphrase.empty()) {
|
||||||
|
CSecret psec(passphrase.begin(), passphrase.end());
|
||||||
|
uint256 piv = GetRandHash();
|
||||||
|
std::vector<unsigned char> pcipher;
|
||||||
|
if (!EncryptSecret(vMasterKey, psec, piv, pcipher)) { strError = "Failed to encrypt passphrase."; return false; }
|
||||||
|
hdPassphraseIV = piv; vchCryptedHDPassphrase = pcipher;
|
||||||
|
wdb.WriteHDCryptedPassphrase(piv, pcipher);
|
||||||
|
} else {
|
||||||
|
vchCryptedHDPassphrase.clear();
|
||||||
|
wdb.EraseHDPassphrase(); // re-seed without passphrase: drop any old record
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
wdb.WriteHDMnemonic(m);
|
wdb.WriteHDMnemonic(m);
|
||||||
|
if (!passphrase.empty())
|
||||||
|
wdb.WriteHDPassphrase(passphrase);
|
||||||
|
else
|
||||||
|
wdb.EraseHDPassphrase();
|
||||||
}
|
}
|
||||||
wdb.WriteHDChain(nHDChainIndex);
|
wdb.WriteHDChain(nHDChainIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,9 @@ public:
|
|||||||
std::string hdMnemonic; // in-memory phrase (present when unlocked/unencrypted)
|
std::string hdMnemonic; // in-memory phrase (present when unlocked/unencrypted)
|
||||||
std::vector<unsigned char> vchCryptedHDMnemonic; // encrypted phrase (loaded, decrypted on unlock)
|
std::vector<unsigned char> vchCryptedHDMnemonic; // encrypted phrase (loaded, decrypted on unlock)
|
||||||
uint256 hdMnemonicIV; // IV for the encrypted phrase
|
uint256 hdMnemonicIV; // IV for the encrypted phrase
|
||||||
|
std::string hdPassphrase; // BIP39 "25th word"; empty = none. Same lifecycle as hdMnemonic.
|
||||||
|
std::vector<unsigned char> vchCryptedHDPassphrase; // encrypted passphrase (loaded, decrypted on unlock)
|
||||||
|
uint256 hdPassphraseIV; // IV for the encrypted passphrase
|
||||||
|
|
||||||
// check whether we are allowed to upgrade (or already support) to the named feature
|
// check whether we are allowed to upgrade (or already support) to the named feature
|
||||||
bool CanSupportFeature(WalletFeature wf) { return nWalletMaxVersion >= static_cast<int>(wf); }
|
bool CanSupportFeature(WalletFeature wf) { return nWalletMaxVersion >= static_cast<int>(wf); }
|
||||||
@@ -150,6 +153,8 @@ public:
|
|||||||
bool DeriveHDKey(int64_t index, CKey& keyOut) const;
|
bool DeriveHDKey(int64_t index, CKey& keyOut) const;
|
||||||
bool LoadHDMnemonic(const std::string& m) { hdMnemonic = m; fHDEnabled = true; return true; }
|
bool LoadHDMnemonic(const std::string& m) { hdMnemonic = m; fHDEnabled = true; return true; }
|
||||||
bool LoadCryptedHDMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) { hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher; fHDEnabled = true; return true; }
|
bool LoadCryptedHDMnemonic(const uint256& iv, const std::vector<unsigned char>& cipher) { hdMnemonicIV = iv; vchCryptedHDMnemonic = cipher; fHDEnabled = true; return true; }
|
||||||
|
bool LoadHDPassphrase(const std::string& p) { hdPassphrase = p; return true; }
|
||||||
|
bool LoadCryptedHDPassphrase(const uint256& iv, const std::vector<unsigned char>& cipher) { hdPassphraseIV = iv; vchCryptedHDPassphrase = cipher; return true; }
|
||||||
// Adds a key to the store, and saves it to disk.
|
// Adds a key to the store, and saves it to disk.
|
||||||
bool AddKey(const CKey& key);
|
bool AddKey(const CKey& key);
|
||||||
// Adds a key to the store, without saving it to disk (used by LoadWallet)
|
// Adds a key to the store, without saving it to disk (used by LoadWallet)
|
||||||
|
|||||||
+10
-1
@@ -265,7 +265,8 @@ static bool IsKeyType(const std::string& strType)
|
|||||||
{
|
{
|
||||||
return (strType == "key" || strType == "wkey" ||
|
return (strType == "key" || strType == "wkey" ||
|
||||||
strType == "mkey" || strType == "ckey" ||
|
strType == "mkey" || strType == "ckey" ||
|
||||||
strType == "hdmnemonic" || strType == "hdcmnemonic");
|
strType == "hdmnemonic" || strType == "hdcmnemonic" ||
|
||||||
|
strType == "hdpassphrase" || strType == "hdcpassphrase");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||||
@@ -414,6 +415,14 @@ static bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssVa
|
|||||||
std::pair<uint256, std::vector<unsigned char>> cm;
|
std::pair<uint256, std::vector<unsigned char>> cm;
|
||||||
ssValue >> cm;
|
ssValue >> cm;
|
||||||
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
pwallet->LoadCryptedHDMnemonic(cm.first, cm.second);
|
||||||
|
} else if (strType == "hdpassphrase") {
|
||||||
|
std::string p;
|
||||||
|
ssValue >> p;
|
||||||
|
pwallet->LoadHDPassphrase(p);
|
||||||
|
} else if (strType == "hdcpassphrase") {
|
||||||
|
std::pair<uint256, std::vector<unsigned char>> cp;
|
||||||
|
ssValue >> cp;
|
||||||
|
pwallet->LoadCryptedHDPassphrase(cp.first, cp.second);
|
||||||
} else if (strType == "hdchain") {
|
} else if (strType == "hdchain") {
|
||||||
int64_t n;
|
int64_t n;
|
||||||
ssValue >> n;
|
ssValue >> n;
|
||||||
|
|||||||
@@ -178,6 +178,25 @@ public:
|
|||||||
nWalletDBUpdated++;
|
nWalletDBUpdated++;
|
||||||
return Write(std::string("hdchain"), nIndex);
|
return Write(std::string("hdchain"), nIndex);
|
||||||
}
|
}
|
||||||
|
// BIP39 passphrase ("25th word"). Same plaintext/crypted lifecycle as the
|
||||||
|
// mnemonic: exactly one of the two records exists at a time; both absent
|
||||||
|
// means no passphrase (legacy wallets and the common case).
|
||||||
|
bool WriteHDPassphrase(const std::string& passphrase) {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
Erase(std::string("hdcpassphrase"));
|
||||||
|
return Write(std::string("hdpassphrase"), passphrase);
|
||||||
|
}
|
||||||
|
bool WriteHDCryptedPassphrase(const uint256& iv, const std::vector<unsigned char>& cipher) {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
Erase(std::string("hdpassphrase"));
|
||||||
|
return Write(std::string("hdcpassphrase"), std::make_pair(iv, cipher));
|
||||||
|
}
|
||||||
|
bool EraseHDPassphrase() {
|
||||||
|
nWalletDBUpdated++;
|
||||||
|
Erase(std::string("hdpassphrase"));
|
||||||
|
Erase(std::string("hdcpassphrase"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
bool ReadPool(int64_t nPool, CKeyPool& keypool)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user