upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/db.h
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-04 10:01:36 -0300
committerGitHub <noreply@github.com>2026-08-04 10:01:36 -0300
commit1db5f372c36eb701602a70a358de2344726ed734 (patch)
tree9d028ed3097ac51368ca312ff439664c76f439b7 /src/db.h
parent9176d5b3f32b907e4bafde5152828b2ae04ae8a1 (diff)
wallet encryption: the stack from #130–#133, with the two blockers fixed (#134)
* wallet: refuse future encrypted wallet formats * wallet: add encrypted key unlock core * wallet: add encryptwallet rewrite command * wallet: read passphrases from stdin or files * wallet: keep the derivation across encryption, and take the logs with it Two things stopped the encryption stack from being safe to merge. Both were found against a wallet holding real coin, not in the self-tests. 1. Encrypting turned a BIP44 wallet into a legacy one. LoadWallet() ends by resetting the schema and the derivation counters when no seed is in memory. That was written when "no seed after loading" could only mean the wallet had none. An encrypted wallet has a seed -- it just has not been decrypted yet -- so the reset threw away metadata that had been read correctly a moment earlier. Unlocking later restores the seed and nothing restores the rest. Measured on the wallet from the #124 proof: after encrypting, index 1 moved off BAtgSgra2X8Dqo5PJzPCLJhPwrrC2thYVJ, the address actually holding 1.00 BTF, and the audit went from "1.00 covered by the phrase" to "0.00 covered, 1.00 wallet.dat-only". Encrypt your wallet and the program told you your recovery phrase no longer covered your money. Now the condition also accepts a wallet whose seed is present but encrypted. Same wallet, before and after: schema bip44, same four derived addresses, 1.00 BTF still reported as covered. 2. The plaintext keys stayed behind in the Berkeley DB logs. wallet.dat comes out clean -- verified by scanning the raw file for the 118 bytes of a known private key. database/log.0000000002 did not: it held the whole key, and still did after a full node restart, because every record this wallet ever wrote passed through the log. That defeats the feature for the case it exists for, since our own backup advice is to copy the whole directory rather than wallet.dat alone. log_archive() cannot fix it: it only drops logs Berkeley DB considers obsolete and never the one it is writing to, and with one 10 MB log per directory that is precisely where the keys are. They also did not get there at encryption time -- they accumulated over the wallet's whole life -- so nothing done during the rewrite can archive them out. So the encrypt command now closes the environment and deletes the logs on its way out, after the flush that makes every .dat self-contained. This is not secure erasure; the blocks are unlinked, not overwritten. It closes the case of someone reading a copied data directory. Getting there uncovered a third bug, older than both. DBFlush(true) is not safe to call twice. The first call closes the environment, and txn_checkpoint() on a closed environment segfaults instead of returning an error, so the try/catch around it never sees anything. EncryptWallet() already flushes before renaming the wallet, and the command flushes again on the way out -- that pair crashed every single time. Guarded now. The same function has been calling log_archive() with the address of an uninitialised pointer since 0.1.0, on every clean shutdown, in a path whose catch(...) cannot catch a structured fault on Windows. DB_ARCH_REMOVE requires that argument to be NULL. Fixed while here, because the encryption cleanup depends on that call not being a coin flip. --------- Co-authored-by: Gilcimar Duarte <ti@gpm.infra>
Diffstat (limited to 'src/db.h')
-rw-r--r--src/db.h58
1 files changed, 58 insertions, 0 deletions
diff --git a/src/db.h b/src/db.h
index 90238bf..b184b17 100644
--- a/src/db.h
+++ b/src/db.h
@@ -16,9 +16,43 @@ class CWalletTx;
16extern map<string, string> mapAddressBook; 16extern map<string, string> mapAddressBook;
17extern bool fClient; 17extern bool fClient;
18 18
19// Wallet file format guard. The current wallet stores private keys in plain
20// records. The next format will encrypt them; builds that do not implement it
21// must stop before they can show an empty-looking wallet.
22static const int WALLET_FORMAT_PLAINTEXT = 1;
23static const int WALLET_FORMAT_ENCRYPTED = 2;
24static const int WALLET_FORMAT_SUPPORTED = WALLET_FORMAT_ENCRYPTED;
25
26class CWalletMasterKey
27{
28public:
29 vector<unsigned char> vchCryptedKey;
30 vector<unsigned char> vchSalt;
31 unsigned int nDerivationMethod;
32 unsigned int nDeriveIterations;
33
34 CWalletMasterKey()
35 {
36 nDerivationMethod = 0;
37 nDeriveIterations = 25000;
38 vchSalt.resize(8);
39 }
40
41 IMPLEMENT_SERIALIZE
42 (
43 READWRITE(vchCryptedKey);
44 READWRITE(vchSalt);
45 READWRITE(nDerivationMethod);
46 READWRITE(nDeriveIterations);
47 )
48};
49
19 50
20extern DbEnv dbenv; 51extern DbEnv dbenv;
21extern void DBFlush(bool fShutdown); 52extern void DBFlush(bool fShutdown);
53// Close the environment and delete its logs. Only the encryption command calls
54// this: the logs hold the plaintext records the old wallet wrote.
55extern void PurgeDbEnvironmentLogs();
22 56
23 57
24 58
@@ -411,6 +445,29 @@ public:
411 && Write(string("hdchaincode"), vchChainCode); 445 && Write(string("hdchaincode"), vchChainCode);
412 } 446 }
413 447
448 bool WriteWalletMinVersion(int nVersion)
449 {
450 return Write(string("walletminversion"), nVersion);
451 }
452
453 bool WriteMasterKey(unsigned int nID, const CWalletMasterKey& kMasterKey)
454 {
455 return Write(make_pair(string("mkey"), nID), kMasterKey, true);
456 }
457
458 bool WriteCryptedKey(const vector<unsigned char>& vchPubKey,
459 const vector<unsigned char>& vchCryptedSecret)
460 {
461 return Write(make_pair(string("ckey"), vchPubKey), vchCryptedSecret, false);
462 }
463
464 bool WriteCryptedHDMaster(const vector<unsigned char>& vchCryptedMaster,
465 const vector<unsigned char>& vchCryptedChainCode)
466 {
467 return Write(string("cryptedhdmaster"), vchCryptedMaster)
468 && Write(string("cryptedhdchaincode"), vchCryptedChainCode);
469 }
470
414 bool WriteHDNext(unsigned int nNext) 471 bool WriteHDNext(unsigned int nNext)
415 { 472 {
416 return Write(string("hdnext"), nNext); 473 return Write(string("hdnext"), nNext);
@@ -478,6 +535,7 @@ bool BackupWallet(const string& strDest);
478// POSIX -- the output holds private keys in the clear. 535// POSIX -- the output holds private keys in the clear.
479bool DumpWallet(const string& strDest); 536bool DumpWallet(const string& strDest);
480bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet); 537bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet);
538bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet);
481 539
482inline bool SetAddressBookName(const string& strAddress, const string& strName) 540inline bool SetAddressBookName(const string& strAddress, const string& strName)
483{ 541{