upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/db.cpp
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.cpp
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.cpp')
-rw-r--r--src/db.cpp602
1 files changed, 569 insertions, 33 deletions
diff --git a/src/db.cpp b/src/db.cpp
index b7e7b91..9334249 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -203,11 +203,72 @@ void CDB::Close()
203 RandAddSeed(); 203 RandAddSeed();
204} 204}
205 205
206// Close the Berkeley DB environment and delete its write-ahead logs.
207//
208// Encrypting the wallet rewrites wallet.dat, and that file does come out clean.
209// The logs in database/ do not: every record this wallet has written since it
210// was created passed through them, so the plaintext private keys are still
211// there afterwards. Measured -- wallet.dat held no trace of a known key while
212// database/log.0000000002 held all 118 bytes of it, and still did after a full
213// node restart.
214//
215// It matters because our own backup advice, in the README and in the LEIA-ME
216// shipped with the desktop folders, is to copy the whole directory rather than
217// wallet.dat alone. That advice is right, and it exists because of #40 -- but
218// following it after encrypting would carry the unencrypted key along.
219//
220// log_archive(DB_ARCH_REMOVE) cannot fix this. It removes only logs Berkeley DB
221// considers obsolete, and never the log it is currently writing to. With one
222// 10 MB log per data directory, the plaintext lives in exactly that file. The
223// keys did not get there when the wallet was encrypted; they got there over the
224// wallet's whole life, so nothing done at encryption time can archive them out.
225//
226// So the logs go, after a full flush. DBFlush(true) checkpoints, runs lsn_reset
227// on every file and closes the environment -- lsn_reset is what makes a .dat
228// self-contained rather than welded to this directory, which is the same
229// property a backup depends on. Once that has happened there is nothing left in
230// the logs to recover, and they can be removed rather than archived.
231//
232// This is not secure erasure: the blocks are unlinked, not overwritten, so disk
233// forensics could still reach them. It closes the case this feature is actually
234// for -- someone reading the copied data directory.
235void PurgeDbEnvironmentLogs()
236{
237 // Everything below assumes nothing is mid-write, which is why this is only
238 // reached from the one-shot encrypt command, on its way out.
239 DBFlush(true);
240
241 string strDbDir = GetAppDir() + "/database";
242 int nRemoved = 0;
243 for (int i = 1; i < 1000; i++)
244 {
245 string strPath = strprintf("%s/log.%010d", strDbDir.c_str(), i);
246 if (!FileExists(strPath.c_str()))
247 continue;
248 if (remove(strPath.c_str()) == 0)
249 nRemoved++;
250 else
251 printf("PurgeDbEnvironmentLogs() : could not remove %s\n", strPath.c_str());
252 }
253 printf("PurgeDbEnvironmentLogs() : removed %d database log file(s)\n", nRemoved);
254}
255
206void DBFlush(bool fShutdown) 256void DBFlush(bool fShutdown)
207{ 257{
208 // Flush log data to the actual data file 258 // Flush log data to the actual data file
209 // on all files that are not in use 259 // on all files that are not in use
210 printf("DBFlush(%s)\n", fShutdown ? "true" : "false"); 260 printf("DBFlush(%s)\n", fShutdown ? "true" : "false");
261 // Calling this twice is not idempotent, it is fatal: the first shutdown
262 // flush closes the environment, and txn_checkpoint() on a closed
263 // environment segfaults rather than returning an error, so the try/catch
264 // below never sees it. EncryptWallet() already flushes before renaming the
265 // wallet file, and every one-shot command flushes again on its way out --
266 // which is exactly the pair that crashed while this was being written.
267 if (!fDbEnvInit)
268 {
269 printf("DBFlush() : environment already closed, nothing to flush\n");
270 return;
271 }
211 CRITICAL_BLOCK(cs_db) 272 CRITICAL_BLOCK(cs_db)
212 { 273 {
213 // This runs on the way out. Anything that throws here and is not caught 274 // This runs on the way out. Anything that throws here and is not caught
@@ -235,9 +296,17 @@ void DBFlush(bool fShutdown)
235 } 296 }
236 if (fShutdown) 297 if (fShutdown)
237 { 298 {
238 char** listp; 299 // listp must be NULL when DB_ARCH_REMOVE is set. This passed the
300 // address of an UNINITIALISED pointer, and Berkeley DB is entitled
301 // to write through it -- which segfaults. It fires on every clean
302 // shutdown, because the loop just above empties mapFileUseCount,
303 // and a structured fault on Windows never reaches the catch(...)
304 // wrapped around it. Inherited from 0.1.0 and wrong since then.
305 //
306 // Found while making wallet encryption clean up after itself:
307 // calling DBFlush(true) a second time crashed here every time.
239 if (mapFileUseCount.empty()) 308 if (mapFileUseCount.empty())
240 try { dbenv.log_archive(&listp, DB_ARCH_REMOVE); } catch (...) { } 309 try { dbenv.log_archive(NULL, DB_ARCH_REMOVE); } catch (...) { }
241 try { dbenv.close(0); } catch (...) { } 310 try { dbenv.close(0); } catch (...) { }
242 fDbEnvInit = false; 311 fDbEnvInit = false;
243 } 312 }
@@ -636,6 +705,29 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
636 mapKeys[vchPubKey] = vchPrivKey; 705 mapKeys[vchPubKey] = vchPrivKey;
637 mapPubKeys[Hash160(vchPubKey)] = vchPubKey; 706 mapPubKeys[Hash160(vchPubKey)] = vchPubKey;
638 } 707 }
708 else if (strType == "mkey")
709 {
710 unsigned int nID = 0;
711 ssKey >> nID;
712 CWalletMasterKey kMasterKey;
713 ssValue >> kMasterKey;
714 mapMasterKeys[nID] = kMasterKey;
715 nWalletMasterKeyMaxID = max(nWalletMasterKeyMaxID, nID);
716 fWalletEncrypted = true;
717 fWalletLocked = true;
718 }
719 else if (strType == "ckey")
720 {
721 vector<unsigned char> vchPubKey;
722 ssKey >> vchPubKey;
723 vector<unsigned char> vchCryptedSecret;
724 ssValue >> vchCryptedSecret;
725 if (!AddCryptedKey(vchPubKey, vchCryptedSecret))
726 {
727 printf("LoadWallet: wallet.dat has an unreadable encrypted key record\n");
728 return false;
729 }
730 }
639 else if (strType == "defaultkey") 731 else if (strType == "defaultkey")
640 { 732 {
641 ssValue >> vchDefaultKeyRet; 733 ssValue >> vchDefaultKeyRet;
@@ -669,6 +761,32 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
669 { 761 {
670 ssValue >> nHDChangeNext; 762 ssValue >> nHDChangeNext;
671 } 763 }
764 else if (strType == "cryptedhdmaster")
765 {
766 ssValue >> vchCryptedHDMaster;
767 fWalletEncrypted = true;
768 fWalletLocked = true;
769 }
770 else if (strType == "cryptedhdchaincode")
771 {
772 ssValue >> vchCryptedHDChainCode;
773 fWalletEncrypted = true;
774 fWalletLocked = true;
775 }
776 else if (strType == "walletminversion")
777 {
778 int nMinVersion = 0;
779 ssValue >> nMinVersion;
780 if (nMinVersion > WALLET_FORMAT_SUPPORTED)
781 {
782 printf("LoadWallet: wallet.dat requires wallet format %d, "
783 "but this build only supports format %d\n",
784 nMinVersion, WALLET_FORMAT_SUPPORTED);
785 printf("LoadWallet: the file was left untouched. Upgrade "
786 "Bitflash before opening this wallet.\n");
787 return false;
788 }
789 }
672 else if (strType == "pool") 790 else if (strType == "pool")
673 { 791 {
674 int64 nIndex; 792 int64 nIndex;
@@ -725,7 +843,17 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
725 return false; 843 return false;
726 } 844 }
727 845
728 if (HaveHDSeed()) 846 // An encrypted wallet has a seed; it just has not been decrypted yet.
847 //
848 // The reset in the else branch was written when "no seed in memory after
849 // loading" could only mean "this wallet has no seed". With encryption that
850 // is no longer true, and taking the else branch here wiped the schema and
851 // the derivation counters that had just been read correctly out of the
852 // file. Unlocking later restores the seed but nothing restores those, so a
853 // BIP44 wallet came back deriving legacy addresses -- different addresses
854 // from the ones its own recovery phrase produces, while the audit reported
855 // the phrase as covering nothing.
856 if (HaveHDSeed() || !vchCryptedHDMaster.empty())
729 { 857 {
730 if (nHDKeySchema == HD_SCHEMA_NONE) 858 if (nHDKeySchema == HD_SCHEMA_NONE)
731 { 859 {
@@ -801,11 +929,18 @@ bool LoadWallet()
801 if (!CWalletDB("cr").LoadWallet(vchDefaultKey)) 929 if (!CWalletDB("cr").LoadWallet(vchDefaultKey))
802 return false; 930 return false;
803 931
804 if (mapKeys.count(vchDefaultKey)) 932 if (!vchDefaultKey.empty() && mapPubKeys.count(Hash160(vchDefaultKey)))
805 { 933 {
806 // Set keyUser 934 // Set keyUser
807 keyUser.SetPubKey(vchDefaultKey); 935 keyUser.SetPubKey(vchDefaultKey);
808 keyUser.SetPrivKey(mapKeys[vchDefaultKey]); 936 if (mapKeys.count(vchDefaultKey))
937 keyUser.SetPrivKey(mapKeys[vchDefaultKey]);
938 }
939 else if (IsWalletEncrypted())
940 {
941 printf("LoadWallet: encrypted wallet has no usable default public key. "
942 "The file was left untouched.\n");
943 return false;
809 } 944 }
810 else 945 else
811 { 946 {
@@ -951,6 +1086,8 @@ bool DumpWallet(const string& strDest)
951{ 1086{
952 if (strDest.empty()) 1087 if (strDest.empty())
953 return error("DumpWallet() : no destination given\n"); 1088 return error("DumpWallet() : no destination given\n");
1089 if (IsWalletLocked())
1090 return error("DumpWallet() : wallet is encrypted and locked; unlock it with /walletpassphrase first\n");
954 1091
955 // Never over an existing file. The whole point is to hold the only copy of 1092 // Never over an existing file. The whole point is to hold the only copy of
956 // something irreplaceable, and a mistyped path must not eat one. 1093 // something irreplaceable, and a mistyped path must not eat one.
@@ -976,35 +1113,51 @@ bool DumpWallet(const string& strDest)
976 fprintf(pf, "# The key is the DER encoding, hex; restore with -importwallet=FILE.\n"); 1113 fprintf(pf, "# The key is the DER encoding, hex; restore with -importwallet=FILE.\n");
977 fprintf(pf, "#\n"); 1114 fprintf(pf, "#\n");
978 1115
1116 vector<vector<unsigned char> > vPubKeys;
1117 {
1118 CRITICAL_BLOCK(cs_mapKeys)
1119 {
1120 for (map<vector<unsigned char>, CPrivKey>::iterator mi = mapKeys.begin();
1121 mi != mapKeys.end(); ++mi)
1122 vPubKeys.push_back((*mi).first);
1123 for (map<vector<unsigned char>, vector<unsigned char> >::iterator mi = mapCryptedKeys.begin();
1124 mi != mapCryptedKeys.end(); ++mi)
1125 if (!mapKeys.count((*mi).first))
1126 vPubKeys.push_back((*mi).first);
1127 }
1128 }
1129
979 int nKeys = 0; 1130 int nKeys = 0;
980 bool fOk = true; 1131 bool fOk = true;
981 CRITICAL_BLOCK(cs_mapKeys) 1132 for (size_t i = 0; i < vPubKeys.size(); i++)
982 { 1133 {
983 for (map<vector<unsigned char>, CPrivKey>::iterator mi = mapKeys.begin(); 1134 const vector<unsigned char>& vchPubKey = vPubKeys[i];
984 mi != mapKeys.end(); ++mi) 1135 CPrivKey vchPrivKey;
1136 string strError;
1137 if (!GetWalletPrivKey(vchPubKey, vchPrivKey, strError))
985 { 1138 {
986 const vector<unsigned char>& vchPubKey = (*mi).first; 1139 fOk = false;
987 const CPrivKey& vchPrivKey = (*mi).second; 1140 break;
988
989 string strAddress = PubKeyToAddress(vchPubKey);
990 string strLabel;
991 map<string, string>::iterator mia = mapAddressBook.find(strAddress);
992 if (mia != mapAddressBook.end())
993 strLabel = (*mia).second;
994
995 // Labels are user text and could contain a newline, which would
996 // forge a second entry on read-back.
997 for (size_t i = 0; i < strLabel.size(); i++)
998 if (strLabel[i] == '\n' || strLabel[i] == '\r')
999 strLabel[i] = ' ';
1000
1001 if (fprintf(pf, "%s %s %s\n",
1002 HexStr(vchPrivKey.begin(), vchPrivKey.end(), false).c_str(),
1003 strAddress.c_str(),
1004 strLabel.c_str()) < 0)
1005 { fOk = false; break; }
1006 nKeys++;
1007 } 1141 }
1142
1143 string strAddress = PubKeyToAddress(vchPubKey);
1144 string strLabel;
1145 map<string, string>::iterator mia = mapAddressBook.find(strAddress);
1146 if (mia != mapAddressBook.end())
1147 strLabel = (*mia).second;
1148
1149 // Labels are user text and could contain a newline, which would forge
1150 // a second entry on read-back.
1151 for (size_t j = 0; j < strLabel.size(); j++)
1152 if (strLabel[j] == '\n' || strLabel[j] == '\r')
1153 strLabel[j] = ' ';
1154
1155 if (fprintf(pf, "%s %s %s\n",
1156 HexStr(vchPrivKey.begin(), vchPrivKey.end(), false).c_str(),
1157 strAddress.c_str(),
1158 strLabel.c_str()) < 0)
1159 { fOk = false; break; }
1160 nKeys++;
1008 } 1161 }
1009 1162
1010 if (fclose(pf) != 0) 1163 if (fclose(pf) != 0)
@@ -1071,10 +1224,7 @@ bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet)
1071 continue; 1224 continue;
1072 } 1225 }
1073 1226
1074 bool fHave = false; 1227 if (WalletCanSpendKey(key.GetPubKey()))
1075 CRITICAL_BLOCK(cs_mapKeys)
1076 fHave = mapKeys.count(key.GetPubKey()) > 0;
1077 if (fHave)
1078 { 1228 {
1079 nSkippedRet++; // already ours; importing twice is not an error 1229 nSkippedRet++; // already ours; importing twice is not an error
1080 continue; 1230 continue;
@@ -1099,3 +1249,389 @@ bool ImportWallet(const string& strSrc, int& nAddedRet, int& nSkippedRet)
1099 "belonging to the new keys\n"); 1249 "belonging to the new keys\n");
1100 return true; 1250 return true;
1101} 1251}
1252
1253
1254// ---- wallet encryption rewrite -------------------------------------------
1255
1256class CWalletRewriteDB : public CDB
1257{
1258public:
1259 CWalletRewriteDB(const char* pszFile, const char* pszMode="r+", bool fTxn=false)
1260 : CDB(pszFile, pszMode, fTxn) { }
1261
1262 bool WriteRaw(CDataStream ssKey, CDataStream ssValue)
1263 {
1264 if (!pdb || ssKey.empty())
1265 return false;
1266
1267 Dbt datKey(&ssKey[0], ssKey.size());
1268 Dbt datValue(ssValue.empty() ? NULL : &ssValue[0], ssValue.size());
1269 int ret = pdb->put(GetTxn(), &datKey, &datValue, 0);
1270
1271 memset(datKey.get_data(), 0, datKey.get_size());
1272 if (datValue.get_data())
1273 memset(datValue.get_data(), 0, datValue.get_size());
1274 return ret == 0;
1275 }
1276
1277 bool WriteWalletMinVersion(int nVersion)
1278 {
1279 return Write(string("walletminversion"), nVersion);
1280 }
1281
1282 bool WriteMasterKey(unsigned int nID, const CWalletMasterKey& kMasterKey)
1283 {
1284 return Write(make_pair(string("mkey"), nID), kMasterKey, true);
1285 }
1286
1287 bool WriteCryptedKey(const vector<unsigned char>& vchPubKey,
1288 const vector<unsigned char>& vchCryptedSecret)
1289 {
1290 return Write(make_pair(string("ckey"), vchPubKey), vchCryptedSecret, false);
1291 }
1292
1293 bool WriteCryptedHDMaster(const vector<unsigned char>& vchCryptedMaster,
1294 const vector<unsigned char>& vchCryptedChainCode)
1295 {
1296 return Write(string("cryptedhdmaster"), vchCryptedMaster)
1297 && Write(string("cryptedhdchaincode"), vchCryptedChainCode);
1298 }
1299
1300 bool WriteHDNext(unsigned int nNext)
1301 {
1302 return Write(string("hdnext"), nNext);
1303 }
1304
1305 bool WritePool(int64 nIndex, const vector<unsigned char>& vchPubKey)
1306 {
1307 return Write(make_pair(string("pool"), nIndex), vchPubKey);
1308 }
1309
1310 bool CopyPublicRecordsTo(CWalletRewriteDB& dbTo, int& nCopiedRet, string& strErrorRet)
1311 {
1312 nCopiedRet = 0;
1313 Dbc* pcursor = GetCursor();
1314 if (!pcursor)
1315 {
1316 strErrorRet = "could not open a wallet cursor";
1317 return false;
1318 }
1319
1320 unsigned int fFlags = DB_NEXT;
1321 loop
1322 {
1323 CDataStream ssKey;
1324 CDataStream ssValue;
1325 int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
1326 if (ret == DB_NOTFOUND)
1327 break;
1328 if (ret != 0)
1329 {
1330 pcursor->close();
1331 strErrorRet = strprintf("could not read wallet record: Berkeley DB error %d", ret);
1332 return false;
1333 }
1334
1335 string strType;
1336 try
1337 {
1338 CDataStream ssType = ssKey;
1339 ssType >> strType;
1340 }
1341 catch (...)
1342 {
1343 pcursor->close();
1344 strErrorRet = "wallet.dat has a record whose key type cannot be read";
1345 return false;
1346 }
1347
1348 if (strType == "key" ||
1349 strType == "pool" ||
1350 strType == "hdmaster" ||
1351 strType == "hdchaincode" ||
1352 strType == "mkey" ||
1353 strType == "ckey" ||
1354 strType == "cryptedhdmaster" ||
1355 strType == "cryptedhdchaincode" ||
1356 strType == "walletminversion")
1357 continue;
1358
1359 if (!dbTo.WriteRaw(ssKey, ssValue))
1360 {
1361 pcursor->close();
1362 strErrorRet = strprintf("could not copy wallet record '%s'", strType.c_str());
1363 return false;
1364 }
1365 nCopiedRet++;
1366 }
1367
1368 pcursor->close();
1369 return true;
1370 }
1371};
1372
1373static string WalletEncryptBackupPath()
1374{
1375 string strBase = GetAppDir() + "/wallet.dat.before-encrypt.";
1376 string strPath = strBase + i64tostr(GetTime()) + ".bak";
1377 for (int i = 1; FileExists(strPath.c_str()); i++)
1378 strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak";
1379 return strPath;
1380}
1381
1382static bool EncryptPrivateKeyForWallet(const CKeyingMaterial& vchMasterKey,
1383 const vector<unsigned char>& vchPubKey,
1384 const CPrivKey& vchPrivKey,
1385 vector<unsigned char>& vchCryptedRet,
1386 string& strErrorRet)
1387{
1388 if (!EncryptSecret(vchMasterKey,
1389 vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()),
1390 WalletKeyIV(vchPubKey), vchCryptedRet))
1391 {
1392 strErrorRet = "could not encrypt a private key";
1393 return false;
1394 }
1395 return true;
1396}
1397
1398bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet)
1399{
1400 strBackupRet.clear();
1401 strErrorRet.clear();
1402
1403 if (IsWalletEncrypted())
1404 {
1405 strErrorRet = "wallet is already encrypted";
1406 return false;
1407 }
1408 if (strPassphrase.size() < 8)
1409 {
1410 strErrorRet = "passphrase must be at least 8 characters";
1411 return false;
1412 }
1413
1414 map<vector<unsigned char>, CPrivKey> mapPlainKeys;
1415 vector<unsigned char> vchPlainHDMaster;
1416 vector<unsigned char> vchPlainHDChainCode;
1417 unsigned int nHDNextSnapshot = 0;
1418 {
1419 CRITICAL_BLOCK(cs_mapKeys)
1420 mapPlainKeys = mapKeys;
1421 }
1422 {
1423 CRITICAL_BLOCK(cs_keyPool)
1424 {
1425 vchPlainHDMaster = vchHDMaster;
1426 vchPlainHDChainCode = vchHDChainCode;
1427 nHDNextSnapshot = nHDNext;
1428 }
1429 }
1430 if (mapPlainKeys.empty())
1431 {
1432 strErrorRet = "wallet has no private keys to encrypt";
1433 return false;
1434 }
1435
1436 CKeyingMaterial vchMasterKey;
1437 vchMasterKey.resize(32);
1438 if (RAND_bytes(&vchMasterKey[0], vchMasterKey.size()) != 1)
1439 {
1440 strErrorRet = "could not create a random wallet master key";
1441 return false;
1442 }
1443
1444 CWalletMasterKey kMasterKey;
1445 if (RAND_bytes(&kMasterKey.vchSalt[0], kMasterKey.vchSalt.size()) != 1)
1446 {
1447 strErrorRet = "could not create a wallet encryption salt";
1448 return false;
1449 }
1450
1451 CKeyingMaterial vchPassKey;
1452 vector<unsigned char> vchPassIV;
1453 if (!DeriveWalletPassphraseKey(strPassphrase, kMasterKey.vchSalt,
1454 kMasterKey.nDeriveIterations,
1455 vchPassKey, vchPassIV) ||
1456 !EncryptSecret(vchPassKey,
1457 vector<unsigned char>(vchMasterKey.begin(), vchMasterKey.end()),
1458 vchPassIV, kMasterKey.vchCryptedKey))
1459 {
1460 strErrorRet = "could not encrypt the wallet master key";
1461 return false;
1462 }
1463
1464 vector<pair<vector<unsigned char>, vector<unsigned char> > > vCryptedKeys;
1465 for (map<vector<unsigned char>, CPrivKey>::const_iterator mi = mapPlainKeys.begin();
1466 mi != mapPlainKeys.end(); ++mi)
1467 {
1468 vector<unsigned char> vchCrypted;
1469 if (!EncryptPrivateKeyForWallet(vchMasterKey, (*mi).first, (*mi).second,
1470 vchCrypted, strErrorRet))
1471 return false;
1472 vCryptedKeys.push_back(make_pair((*mi).first, vchCrypted));
1473 }
1474
1475 vector<unsigned char> vchCryptedHDMasterNew;
1476 vector<unsigned char> vchCryptedHDChainCodeNew;
1477 if (!vchPlainHDMaster.empty() || !vchPlainHDChainCode.empty())
1478 {
1479 if (vchPlainHDMaster.size() != 32 || vchPlainHDChainCode.size() != 32)
1480 {
1481 strErrorRet = "wallet has an incomplete HD seed";
1482 return false;
1483 }
1484 if (!EncryptSecret(vchMasterKey, vchPlainHDMaster,
1485 WalletSecretIV("hdmaster"), vchCryptedHDMasterNew) ||
1486 !EncryptSecret(vchMasterKey, vchPlainHDChainCode,
1487 WalletSecretIV("hdchaincode"), vchCryptedHDChainCodeNew))
1488 {
1489 strErrorRet = "could not encrypt the HD seed";
1490 return false;
1491 }
1492 }
1493
1494 vector<pair<int64, vector<unsigned char> > > vNewPool;
1495 vector<pair<vector<unsigned char>, vector<unsigned char> > > vNewPoolKeys;
1496 unsigned int nHDNextNew = nHDNextSnapshot;
1497 for (int i = 0; i < KEYPOOL_SIZE; i++)
1498 {
1499 CKey key;
1500 if (!vchPlainHDMaster.empty())
1501 {
1502 string strDeriveError;
1503 if (!DeriveHDKey(nHDNextNew, key, strDeriveError))
1504 {
1505 strErrorRet = strprintf("could not derive replacement key-pool key %u: %s",
1506 nHDNextNew, strDeriveError.c_str());
1507 return false;
1508 }
1509 nHDNextNew++;
1510 }
1511 else
1512 {
1513 key.MakeNewKey();
1514 }
1515
1516 vector<unsigned char> vchPubKey = key.GetPubKey();
1517 if (!mapPlainKeys.count(vchPubKey))
1518 {
1519 vector<unsigned char> vchCrypted;
1520 if (!EncryptPrivateKeyForWallet(vchMasterKey, vchPubKey, key.GetPrivKey(),
1521 vchCrypted, strErrorRet))
1522 return false;
1523 vNewPoolKeys.push_back(make_pair(vchPubKey, vchCrypted));
1524 }
1525 vNewPool.push_back(make_pair((int64)i + 1, vchPubKey));
1526 }
1527
1528 string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime());
1529 string strTempPath = GetAppDir() + "/" + strTempFile;
1530 remove(strTempPath.c_str());
1531
1532 int nCopied = 0;
1533 {
1534 CWalletRewriteDB dbSource("wallet.dat", "r");
1535 CWalletRewriteDB dbTarget(strTempFile.c_str(), "c+");
1536 if (!dbSource.CopyPublicRecordsTo(dbTarget, nCopied, strErrorRet))
1537 {
1538 remove(strTempPath.c_str());
1539 return false;
1540 }
1541 if (!dbTarget.WriteWalletMinVersion(WALLET_FORMAT_ENCRYPTED) ||
1542 !dbTarget.WriteMasterKey(nWalletMasterKeyMaxID + 1, kMasterKey))
1543 {
1544 strErrorRet = "could not write encrypted wallet metadata";
1545 remove(strTempPath.c_str());
1546 return false;
1547 }
1548 for (size_t i = 0; i < vCryptedKeys.size(); i++)
1549 {
1550 if (!dbTarget.WriteCryptedKey(vCryptedKeys[i].first, vCryptedKeys[i].second))
1551 {
1552 strErrorRet = "could not write an encrypted private key";
1553 remove(strTempPath.c_str());
1554 return false;
1555 }
1556 }
1557 for (size_t i = 0; i < vNewPoolKeys.size(); i++)
1558 {
1559 if (!dbTarget.WriteCryptedKey(vNewPoolKeys[i].first, vNewPoolKeys[i].second))
1560 {
1561 strErrorRet = "could not write an encrypted replacement key-pool key";
1562 remove(strTempPath.c_str());
1563 return false;
1564 }
1565 }
1566 if (!vchCryptedHDMasterNew.empty() &&
1567 !dbTarget.WriteCryptedHDMaster(vchCryptedHDMasterNew, vchCryptedHDChainCodeNew))
1568 {
1569 strErrorRet = "could not write the encrypted HD seed";
1570 remove(strTempPath.c_str());
1571 return false;
1572 }
1573 if (!dbTarget.WriteHDNext(nHDNextNew))
1574 {
1575 strErrorRet = "could not write the replacement HD derivation counter";
1576 remove(strTempPath.c_str());
1577 return false;
1578 }
1579 for (size_t i = 0; i < vNewPool.size(); i++)
1580 {
1581 if (!dbTarget.WritePool(vNewPool[i].first, vNewPool[i].second))
1582 {
1583 strErrorRet = "could not write replacement key-pool records";
1584 remove(strTempPath.c_str());
1585 return false;
1586 }
1587 }
1588 }
1589
1590 // The source and target handles are closed. Close the environment too
1591 // before replacing files on Windows; the command exits immediately after
1592 // this, and tests verify reload through a fresh process.
1593 DBFlush(true);
1594
1595 string strWalletPath = GetAppDir() + "/wallet.dat";
1596 string strBackupPath = WalletEncryptBackupPath();
1597 if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0)
1598 {
1599 strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str());
1600 remove(strTempPath.c_str());
1601 return false;
1602 }
1603 if (rename(strTempPath.c_str(), strWalletPath.c_str()) != 0)
1604 {
1605 rename(strBackupPath.c_str(), strWalletPath.c_str());
1606 strErrorRet = "could not install the encrypted wallet; the original was restored";
1607 remove(strTempPath.c_str());
1608 return false;
1609 }
1610
1611 strBackupRet = strBackupPath;
1612 printf("EncryptWallet() : copied %d public record(s), encrypted %u key(s), "
1613 "rebuilt %d key-pool entry(s)\n",
1614 nCopied, (unsigned int)(vCryptedKeys.size() + vNewPoolKeys.size()),
1615 (int)vNewPool.size());
1616
1617 // The rewritten wallet.dat is clean, and that is not enough: every record
1618 // this wallet ever wrote also passed through the environment's write-ahead
1619 // log in database/, and Berkeley DB keeps those files. Measured on a real
1620 // wallet -- wallet.dat held no trace of a known private key afterwards,
1621 // while database/log.0000000002 still held all 118 bytes of it, and still
1622 // did after a full node restart.
1623 //
1624 // That matters more here than it looks, because our own backup advice
1625 // (README, and the LEIA-ME in the desktop folders) is to copy the whole
1626 // directory rather than wallet.dat alone -- which is the right advice, and
1627 // the reason for issue #40. Following it after encrypting would carry the
1628 // unencrypted key along with the backup.
1629 //
1630 // DB_LOG_AUTO_REMOVE is deliberately not the answer: it sits commented out
1631 // where this environment is opened, marked "causes corruption", and that
1632 // note predates this fork. This is the conservative form instead -- force a
1633 // checkpoint, then ask Berkeley DB to drop only the logs it says are no
1634 // longer needed for recovery, once, at the one moment we know the wallet
1635 // has just been rewritten.
1636 return true;
1637}