upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile3
-rw-r--r--src/db.cpp602
-rw-r--r--src/db.h58
-rw-r--r--src/main.cpp334
-rw-r--r--src/main.h40
-rw-r--r--src/main_gui.cpp101
-rw-r--r--src/makefile.mingw3
-rw-r--r--src/script.cpp89
-rw-r--r--src/selftest.cpp504
-rw-r--r--src/walletcmd.cpp36
-rw-r--r--src/walletcmd.h4
11 files changed, 1698 insertions, 76 deletions
diff --git a/src/Makefile b/src/Makefile
index 82e7d06..3577dd6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -139,5 +139,8 @@ tests: test_bip32 bitflash-node
139 ./test_bip32 139 ./test_bip32
140 ./bitflash-node -selftest=wallet-keypool 140 ./bitflash-node -selftest=wallet-keypool
141 ./bitflash-node -selftest=wallet-hd 141 ./bitflash-node -selftest=wallet-hd
142 ./bitflash-node -selftest=wallet-format
143 ./bitflash-node -selftest=wallet-crypto
144 ./bitflash-node -selftest=wallet-encrypt
142 ./bitflash-node -selftest=net-message 145 ./bitflash-node -selftest=net-message
143 ./bitflash-node -selftest=consensus-limits 146 ./bitflash-node -selftest=consensus-limits
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}
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{
diff --git a/src/main.cpp b/src/main.cpp
index efece6c..0342650 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -72,7 +72,15 @@ vector<pair<uint256, bool> > vWalletUpdated;
72CCriticalSection cs_mapWallet; 72CCriticalSection cs_mapWallet;
73 73
74map<vector<unsigned char>, CPrivKey> mapKeys; 74map<vector<unsigned char>, CPrivKey> mapKeys;
75map<vector<unsigned char>, vector<unsigned char> > mapCryptedKeys;
75map<uint160, vector<unsigned char> > mapPubKeys; 76map<uint160, vector<unsigned char> > mapPubKeys;
77map<unsigned int, CWalletMasterKey> mapMasterKeys;
78unsigned int nWalletMasterKeyMaxID = 0;
79CKeyingMaterial vWalletMasterKey;
80vector<unsigned char> vchCryptedHDMaster;
81vector<unsigned char> vchCryptedHDChainCode;
82bool fWalletEncrypted = false;
83bool fWalletLocked = true;
76CCriticalSection cs_mapKeys; 84CCriticalSection cs_mapKeys;
77CKey keyUser; 85CKey keyUser;
78 86
@@ -159,6 +167,21 @@ int MinerThreadCount()
159 167
160bool AddKey(const CKey& key) 168bool AddKey(const CKey& key)
161{ 169{
170 if (IsWalletEncrypted())
171 {
172 string strError;
173 CPrivKey vchPrivKey = key.GetPrivKey();
174 vector<unsigned char> vchCryptedSecret;
175 if (IsWalletLocked())
176 return error("AddKey() : wallet is locked\n");
177 if (!EncryptSecret(vWalletMasterKey, vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()),
178 WalletKeyIV(key.GetPubKey()), vchCryptedSecret))
179 return error("AddKey() : encrypting key failed\n");
180 if (!AddCryptedKey(key.GetPubKey(), vchCryptedSecret))
181 return false;
182 return CWalletDB().WriteCryptedKey(key.GetPubKey(), vchCryptedSecret);
183 }
184
162 CRITICAL_BLOCK(cs_mapKeys) 185 CRITICAL_BLOCK(cs_mapKeys)
163 { 186 {
164 mapKeys[key.GetPubKey()] = key.GetPrivKey(); 187 mapKeys[key.GetPubKey()] = key.GetPrivKey();
@@ -167,10 +190,287 @@ bool AddKey(const CKey& key)
167 return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey()); 190 return CWalletDB().WriteKey(key.GetPubKey(), key.GetPrivKey());
168} 191}
169 192
193bool IsWalletEncrypted()
194{
195 return fWalletEncrypted;
196}
197
198bool IsWalletLocked()
199{
200 return fWalletEncrypted && fWalletLocked;
201}
202
203void LockWallet()
204{
205 if (!fWalletEncrypted)
206 return;
207 CRITICAL_BLOCK(cs_mapKeys)
208 {
209 vWalletMasterKey.clear();
210 if (!vchCryptedHDMaster.empty())
211 vchHDMaster.clear();
212 if (!vchCryptedHDChainCode.empty())
213 vchHDChainCode.clear();
214 fWalletLocked = true;
215 }
216}
217
218bool DeriveWalletPassphraseKey(const string& strPassphrase,
219 const vector<unsigned char>& vchSalt,
220 unsigned int nDeriveIterations,
221 CKeyingMaterial& vchKeyRet,
222 vector<unsigned char>& vchIVRet)
223{
224 vchKeyRet.clear();
225 vchIVRet.clear();
226 if (vchSalt.size() != 8 || nDeriveIterations < 1)
227 return false;
228
229 unsigned char chKey[32];
230 unsigned char chIV[16];
231 int n = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &vchSalt[0],
232 (const unsigned char*)strPassphrase.data(),
233 (int)strPassphrase.size(),
234 (int)nDeriveIterations,
235 chKey, chIV);
236 if (n != 32)
237 {
238 memset(chKey, 0, sizeof(chKey));
239 memset(chIV, 0, sizeof(chIV));
240 return false;
241 }
242 vchKeyRet.assign(chKey, chKey + sizeof(chKey));
243 vchIVRet.assign(chIV, chIV + sizeof(chIV));
244 memset(chKey, 0, sizeof(chKey));
245 memset(chIV, 0, sizeof(chIV));
246 return true;
247}
248
249bool EncryptSecret(const CKeyingMaterial& vchKey,
250 const vector<unsigned char>& vchPlaintext,
251 const vector<unsigned char>& vchIV,
252 vector<unsigned char>& vchCiphertextRet)
253{
254 vchCiphertextRet.clear();
255 if (vchKey.size() != 32 || vchIV.size() != 16)
256 return false;
257
258 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
259 if (!ctx)
260 return false;
261
262 bool fOk = false;
263 int nLen = 0;
264 int nFinal = 0;
265 vector<unsigned char> vchOut(vchPlaintext.size() + EVP_CIPHER_block_size(EVP_aes_256_cbc()));
266 if (EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &vchKey[0], &vchIV[0]) &&
267 EVP_EncryptUpdate(ctx, &vchOut[0], &nLen,
268 vchPlaintext.empty() ? NULL : &vchPlaintext[0],
269 (int)vchPlaintext.size()) &&
270 EVP_EncryptFinal_ex(ctx, &vchOut[0] + nLen, &nFinal))
271 {
272 vchOut.resize(nLen + nFinal);
273 vchCiphertextRet.swap(vchOut);
274 fOk = true;
275 }
276 EVP_CIPHER_CTX_free(ctx);
277 return fOk;
278}
279
280bool DecryptSecret(const CKeyingMaterial& vchKey,
281 const vector<unsigned char>& vchCiphertext,
282 const vector<unsigned char>& vchIV,
283 vector<unsigned char>& vchPlaintextRet)
284{
285 vchPlaintextRet.clear();
286 if (vchKey.size() != 32 || vchIV.size() != 16 || vchCiphertext.empty())
287 return false;
288
289 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
290 if (!ctx)
291 return false;
292
293 bool fOk = false;
294 int nLen = 0;
295 int nFinal = 0;
296 vector<unsigned char> vchOut(vchCiphertext.size());
297 if (EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, &vchKey[0], &vchIV[0]) &&
298 EVP_DecryptUpdate(ctx, &vchOut[0], &nLen, &vchCiphertext[0],
299 (int)vchCiphertext.size()) &&
300 EVP_DecryptFinal_ex(ctx, &vchOut[0] + nLen, &nFinal))
301 {
302 vchOut.resize(nLen + nFinal);
303 vchPlaintextRet.swap(vchOut);
304 fOk = true;
305 }
306 EVP_CIPHER_CTX_free(ctx);
307 return fOk;
308}
309
310vector<unsigned char> WalletKeyIV(const vector<unsigned char>& vchPubKey)
311{
312 uint256 hash = Hash(vchPubKey.begin(), vchPubKey.end());
313 vector<unsigned char> vchIV(16);
314 memcpy(&vchIV[0], hash.begin(), vchIV.size());
315 return vchIV;
316}
317
318vector<unsigned char> WalletSecretIV(const string& strLabel)
319{
320 uint256 hash = Hash(strLabel.begin(), strLabel.end());
321 vector<unsigned char> vchIV(16);
322 memcpy(&vchIV[0], hash.begin(), vchIV.size());
323 return vchIV;
324}
325
326bool AddCryptedKey(const vector<unsigned char>& vchPubKey,
327 const vector<unsigned char>& vchCryptedSecret)
328{
329 if (vchPubKey.empty() || vchCryptedSecret.empty())
330 return false;
331 CRITICAL_BLOCK(cs_mapKeys)
332 {
333 fWalletEncrypted = true;
334 mapCryptedKeys[vchPubKey] = vchCryptedSecret;
335 mapPubKeys[Hash160(vchPubKey)] = vchPubKey;
336 }
337 return true;
338}
339
340bool WalletCanSpendKey(const vector<unsigned char>& vchPubKey)
341{
342 CRITICAL_BLOCK(cs_mapKeys)
343 return mapKeys.count(vchPubKey) > 0 || mapCryptedKeys.count(vchPubKey) > 0;
344 return false;
345}
346
347bool GetWalletPrivKey(const vector<unsigned char>& vchPubKey,
348 CPrivKey& vchPrivKeyRet,
349 string& strErrorRet)
350{
351 vchPrivKeyRet.clear();
352 CRITICAL_BLOCK(cs_mapKeys)
353 {
354 map<vector<unsigned char>, CPrivKey>::iterator mi = mapKeys.find(vchPubKey);
355 if (mi != mapKeys.end())
356 {
357 vchPrivKeyRet = (*mi).second;
358 return true;
359 }
360
361 map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.find(vchPubKey);
362 if (mci == mapCryptedKeys.end())
363 {
364 strErrorRet = "private key not found";
365 return false;
366 }
367 if (IsWalletLocked())
368 {
369 strErrorRet = "wallet is locked";
370 return false;
371 }
372
373 vector<unsigned char> vchPlaintext;
374 if (!DecryptSecret(vWalletMasterKey, (*mci).second, WalletKeyIV(vchPubKey), vchPlaintext))
375 {
376 strErrorRet = "could not decrypt private key";
377 return false;
378 }
379
380 CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end());
381 CKey key;
382 if (!key.SetPrivKey(vchPrivKey) || key.GetPubKey() != vchPubKey)
383 {
384 strErrorRet = "decrypted private key does not match its public key";
385 return false;
386 }
387 vchPrivKeyRet = vchPrivKey;
388 return true;
389 }
390 return false;
391}
392
393bool UnlockWallet(const string& strPassphrase, string& strErrorRet)
394{
395 strErrorRet.clear();
396 if (!IsWalletEncrypted())
397 return true;
398
399 CRITICAL_BLOCK(cs_mapKeys)
400 {
401 for (map<unsigned int, CWalletMasterKey>::iterator mi = mapMasterKeys.begin();
402 mi != mapMasterKeys.end(); ++mi)
403 {
404 const CWalletMasterKey& kMasterKey = (*mi).second;
405 CKeyingMaterial vchPassKey;
406 vector<unsigned char> vchPassIV;
407 if (!DeriveWalletPassphraseKey(strPassphrase, kMasterKey.vchSalt,
408 kMasterKey.nDeriveIterations,
409 vchPassKey, vchPassIV))
410 continue;
411
412 vector<unsigned char> vchMasterPlain;
413 if (!DecryptSecret(vchPassKey, kMasterKey.vchCryptedKey,
414 vchPassIV, vchMasterPlain))
415 continue;
416 if (vchMasterPlain.size() != 32)
417 continue;
418
419 CKeyingMaterial vchCandidate(vchMasterPlain.begin(), vchMasterPlain.end());
420 bool fValid = mapCryptedKeys.empty();
421 for (map<vector<unsigned char>, vector<unsigned char> >::iterator mci = mapCryptedKeys.begin();
422 mci != mapCryptedKeys.end(); ++mci)
423 {
424 vector<unsigned char> vchPlaintext;
425 if (!DecryptSecret(vchCandidate, (*mci).second, WalletKeyIV((*mci).first), vchPlaintext))
426 break;
427 CPrivKey vchPrivKey(vchPlaintext.begin(), vchPlaintext.end());
428 CKey key;
429 if (key.SetPrivKey(vchPrivKey) && key.GetPubKey() == (*mci).first)
430 fValid = true;
431 break;
432 }
433 if (fValid)
434 {
435 vWalletMasterKey = vchCandidate;
436 if (!vchCryptedHDMaster.empty())
437 {
438 vector<unsigned char> vchPlainHDMaster;
439 if (!DecryptSecret(vWalletMasterKey, vchCryptedHDMaster,
440 WalletSecretIV("hdmaster"), vchPlainHDMaster) ||
441 vchPlainHDMaster.size() != 32)
442 {
443 strErrorRet = "could not decrypt HD master key";
444 return false;
445 }
446 vchHDMaster = vchPlainHDMaster;
447 }
448 if (!vchCryptedHDChainCode.empty())
449 {
450 vector<unsigned char> vchPlainHDChainCode;
451 if (!DecryptSecret(vWalletMasterKey, vchCryptedHDChainCode,
452 WalletSecretIV("hdchaincode"), vchPlainHDChainCode) ||
453 vchPlainHDChainCode.size() != 32)
454 {
455 strErrorRet = "could not decrypt HD chain code";
456 return false;
457 }
458 vchHDChainCode = vchPlainHDChainCode;
459 }
460 fWalletLocked = false;
461 return true;
462 }
463 }
464 }
465
466 strErrorRet = "passphrase did not unlock the wallet";
467 return false;
468}
469
170static bool WalletAlreadyHasKey(const vector<unsigned char>& vchPubKey) 470static bool WalletAlreadyHasKey(const vector<unsigned char>& vchPubKey)
171{ 471{
172 CRITICAL_BLOCK(cs_mapKeys) 472 CRITICAL_BLOCK(cs_mapKeys)
173 return mapKeys.count(vchPubKey) > 0; 473 return mapKeys.count(vchPubKey) > 0 || mapCryptedKeys.count(vchPubKey) > 0;
174 return false; 474 return false;
175} 475}
176 476
@@ -509,6 +809,12 @@ bool SetHDSeedFromMnemonic(const string& strMnemonic, string& strErrorRet)
509 809
510void TopUpKeyPool() 810void TopUpKeyPool()
511{ 811{
812 if (IsWalletLocked())
813 {
814 printf("TopUpKeyPool() : wallet is encrypted and locked\n");
815 return;
816 }
817
512 CRITICAL_BLOCK(cs_keyPool) 818 CRITICAL_BLOCK(cs_keyPool)
513 { 819 {
514 while ((int)mapKeyPool.size() < KEYPOOL_SIZE) 820 while ((int)mapKeyPool.size() < KEYPOOL_SIZE)
@@ -562,6 +868,12 @@ void TopUpKeyPool()
562 868
563vector<unsigned char> GetKeyFromPool() 869vector<unsigned char> GetKeyFromPool()
564{ 870{
871 if (IsWalletLocked())
872 {
873 printf("GetKeyFromPool() : wallet is encrypted and locked\n");
874 return vector<unsigned char>();
875 }
876
565 CRITICAL_BLOCK(cs_keyPool) 877 CRITICAL_BLOCK(cs_keyPool)
566 { 878 {
567 TopUpKeyPool(); 879 TopUpKeyPool();
@@ -584,7 +896,15 @@ vector<unsigned char> GetKeyFromPool()
584 // locked open elsewhere. Generating on demand is what this code did for 896 // locked open elsewhere. Generating on demand is what this code did for
585 // its whole life, so fall back to it rather than refuse to mine. 897 // its whole life, so fall back to it rather than refuse to mine.
586 printf("GetKeyFromPool() : pool empty, generating a key on demand\n"); 898 printf("GetKeyFromPool() : pool empty, generating a key on demand\n");
587 return GenerateNewKey(); 899 try
900 {
901 return GenerateNewKey();
902 }
903 catch (const std::exception& e)
904 {
905 printf("GetKeyFromPool() : failed to generate a key: %s\n", e.what());
906 return vector<unsigned char>();
907 }
588} 908}
589 909
590 910
@@ -3581,6 +3901,11 @@ bool BitcoinMiner(int nThreadId)
3581 // thread happened to win a block, which meant a backup taken while mining 3901 // thread happened to win a block, which meant a backup taken while mining
3582 // could not spend what the very next block paid. 3902 // could not spend what the very next block paid.
3583 vector<unsigned char> vchPubKey = GetKeyFromPool(); 3903 vector<unsigned char> vchPubKey = GetKeyFromPool();
3904 if (vchPubKey.empty())
3905 {
3906 printf("BitcoinMiner: no wallet key available for coinbase; mining stopped\n");
3907 return false;
3908 }
3584 CBigNum bnExtraNonce = 0; 3909 CBigNum bnExtraNonce = 0;
3585 while (fGenerateBitcoins) 3910 while (fGenerateBitcoins)
3586 { 3911 {
@@ -3759,6 +4084,11 @@ bool BitcoinMiner(int nThreadId)
3759 // them out one at a time under cs_keyPool, which is 4084 // them out one at a time under cs_keyPool, which is
3760 // what keeps concurrent coinbases distinct (#58). 4085 // what keeps concurrent coinbases distinct (#58).
3761 vchPubKey = GetKeyFromPool(); 4086 vchPubKey = GetKeyFromPool();
4087 if (vchPubKey.empty())
4088 {
4089 printf("BitcoinMiner: no replacement wallet key available; mining stopped\n");
4090 fGenerateBitcoins = false;
4091 }
3762 4092
3763 // Process this block the same as if we had received it from another node 4093 // Process this block the same as if we had received it from another node
3764 if (!ProcessBlock(NULL, pblock.release())) 4094 if (!ProcessBlock(NULL, pblock.release()))
diff --git a/src/main.h b/src/main.h
index cca2cad..c458412 100644
--- a/src/main.h
+++ b/src/main.h
@@ -129,6 +129,46 @@ FILE* AppendBlockFile(unsigned int& nFileRet);
129bool AddKey(const CKey& key); 129bool AddKey(const CKey& key);
130vector<unsigned char> GenerateNewKey(); 130vector<unsigned char> GenerateNewKey();
131 131
132// --- Wallet encryption ----------------------------------------------------
133//
134// Plain wallets keep private keys in mapKeys. Encrypted wallets keep public
135// keys visible so balances can still be recognized while locked, and decrypt
136// private keys only after an unlock passphrase has supplied the master key.
137typedef vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
138extern map<vector<unsigned char>, vector<unsigned char> > mapCryptedKeys;
139extern map<unsigned int, CWalletMasterKey> mapMasterKeys;
140extern unsigned int nWalletMasterKeyMaxID;
141extern CKeyingMaterial vWalletMasterKey;
142extern vector<unsigned char> vchCryptedHDMaster;
143extern vector<unsigned char> vchCryptedHDChainCode;
144extern bool fWalletEncrypted;
145extern bool fWalletLocked;
146bool IsWalletEncrypted();
147bool IsWalletLocked();
148void LockWallet();
149bool UnlockWallet(const string& strPassphrase, string& strErrorRet);
150bool DeriveWalletPassphraseKey(const string& strPassphrase,
151 const vector<unsigned char>& vchSalt,
152 unsigned int nDeriveIterations,
153 CKeyingMaterial& vchKeyRet,
154 vector<unsigned char>& vchIVRet);
155bool EncryptSecret(const CKeyingMaterial& vchKey,
156 const vector<unsigned char>& vchPlaintext,
157 const vector<unsigned char>& vchIV,
158 vector<unsigned char>& vchCiphertextRet);
159bool DecryptSecret(const CKeyingMaterial& vchKey,
160 const vector<unsigned char>& vchCiphertext,
161 const vector<unsigned char>& vchIV,
162 vector<unsigned char>& vchPlaintextRet);
163vector<unsigned char> WalletKeyIV(const vector<unsigned char>& vchPubKey);
164vector<unsigned char> WalletSecretIV(const string& strLabel);
165bool AddCryptedKey(const vector<unsigned char>& vchPubKey,
166 const vector<unsigned char>& vchCryptedSecret);
167bool WalletCanSpendKey(const vector<unsigned char>& vchPubKey);
168bool GetWalletPrivKey(const vector<unsigned char>& vchPubKey,
169 CPrivKey& vchPrivKeyRet,
170 string& strErrorRet);
171
132// --- Key pool ------------------------------------------------------------- 172// --- Key pool -------------------------------------------------------------
133// 173//
134// A backup of this wallet only ever contained the keys that existed the moment 174// A backup of this wallet only ever contained the keys that existed the moment
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 6164ad7..a2577ca 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -40,6 +40,60 @@ static string argval2(int argc, char* argv[], const char* keySlash, const char*
40 return argval(argc, argv, keyDash); 40 return argval(argc, argv, keyDash);
41} 41}
42 42
43static bool ReadPassphraseArgument(const string& strArg, string& strPassphraseRet, string& strErrorRet)
44{
45 strPassphraseRet.clear();
46 strErrorRet.clear();
47 if (strArg.empty())
48 {
49 AttachTerminal();
50 fprintf(stderr, "Enter wallet passphrase: ");
51 fflush(stderr);
52 char buf[4096];
53 if (!fgets(buf, sizeof(buf), stdin))
54 {
55 strErrorRet = "could not read passphrase from stdin";
56 return false;
57 }
58 strPassphraseRet = buf;
59 }
60 else if (strArg[0] == '@')
61 {
62 string strPath = strArg.substr(1);
63 FILE* pf = fopen(strPath.c_str(), "rb");
64 if (!pf)
65 {
66 strErrorRet = strprintf("could not read passphrase file %s", strPath.c_str());
67 return false;
68 }
69 char buf[4096];
70 if (!fgets(buf, sizeof(buf), pf))
71 {
72 fclose(pf);
73 strErrorRet = strprintf("passphrase file %s is empty", strPath.c_str());
74 return false;
75 }
76 fclose(pf);
77 strPassphraseRet = buf;
78 }
79 else
80 {
81 strErrorRet = "passphrase literals on the command line are unsafe; use the option without a value to read stdin, or use @FILE";
82 return false;
83 }
84
85 while (!strPassphraseRet.empty() &&
86 (strPassphraseRet[strPassphraseRet.size() - 1] == '\n' ||
87 strPassphraseRet[strPassphraseRet.size() - 1] == '\r'))
88 strPassphraseRet.resize(strPassphraseRet.size() - 1);
89 if (strPassphraseRet.empty())
90 {
91 strErrorRet = "empty passphrase";
92 return false;
93 }
94 return true;
95}
96
43// The real printf, not the one util.h remaps to OutputDebugStringF. Help that 97// The real printf, not the one util.h remaps to OutputDebugStringF. Help that
44// goes to debug.log is help nobody asked for: `-help` printed a full page into 98// goes to debug.log is help nobody asked for: `-help` printed a full page into
45// the data directory and returned 0 with an empty terminal, which reads as a 99// the data directory and returned 0 with an empty terminal, which reads as a
@@ -56,7 +110,7 @@ static void PrintUsage()
56 printf(" /debug\n"); 110 printf(" /debug\n");
57 printf(" /gen\n"); 111 printf(" /gen\n");
58 printf(" /nogui or /daemon\n"); 112 printf(" /nogui or /daemon\n");
59 printf(" /selftest=wallet-keypool or /selftest=wallet-hd\n"); 113 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, or wallet-encrypt\n");
60 printf("\n"); 114 printf("\n");
61 printf("Mining mode:\n"); 115 printf("Mining mode:\n");
62 printf(" /operator\n"); 116 printf(" /operator\n");
@@ -85,6 +139,10 @@ static void PrintUsage()
85 printf(" /dumpwallet=FILE (export private keys as text -- readable by\n"); 139 printf(" /dumpwallet=FILE (export private keys as text -- readable by\n");
86 printf(" anyone, so guard it like cash)\n"); 140 printf(" anyone, so guard it like cash)\n");
87 printf(" /importwallet=FILE (load keys from such a file back in)\n"); 141 printf(" /importwallet=FILE (load keys from such a file back in)\n");
142 printf(" /encryptwallet[=@FILE] (rewrite wallet.dat with encrypted private keys\n");
143 printf(" and encrypted HD seed, then exit)\n");
144 printf(" /walletpassphrase[=@FILE] (unlock an encrypted wallet for one-shot\n");
145 printf(" commands; without @FILE reads stdin)\n");
88 printf(" /newaddress (print the next receiving address, then exit)\n"); 146 printf(" /newaddress (print the next receiving address, then exit)\n");
89 printf(" /sendto=ADDRESS,AMOUNT (spend from this wallet, then exit -- the\n"); 147 printf(" /sendto=ADDRESS,AMOUNT (spend from this wallet, then exit -- the\n");
90 printf(" only way to send without the window)\n"); 148 printf(" only way to send without the window)\n");
@@ -299,6 +357,39 @@ int main(int argc, char* argv[])
299 } 357 }
300 printf("Height=%d\n", nBestHeight); 358 printf("Height=%d\n", nBestHeight);
301 359
360 bool fEncryptWallet = arg(argc, argv, "/encryptwallet") || arg(argc, argv, "-encryptwallet");
361 string strEncryptWallet = argval2(argc, argv, "/encryptwallet", "-encryptwallet");
362 if (fEncryptWallet)
363 {
364 string strPassphrase;
365 string strPassphraseError;
366 if (!ReadPassphraseArgument(strEncryptWallet, strPassphrase, strPassphraseError))
367 {
368 fprintf(stderr, "Cannot read encryption passphrase: %s\n", strPassphraseError.c_str());
369 DBFlush(true);
370 return 1;
371 }
372 int nRet = CmdEncryptWallet(strPassphrase);
373 if (nRet != 0)
374 DBFlush(true);
375 return nRet;
376 }
377
378 bool fWalletPassphrase = arg(argc, argv, "/walletpassphrase") || arg(argc, argv, "-walletpassphrase");
379 string strWalletPassphrase = argval2(argc, argv, "/walletpassphrase", "-walletpassphrase");
380 if (fWalletPassphrase)
381 {
382 string strPassphrase;
383 string strUnlockError;
384 if (!ReadPassphraseArgument(strWalletPassphrase, strPassphrase, strUnlockError) ||
385 !UnlockWallet(strPassphrase, strUnlockError))
386 {
387 fprintf(stderr, "Cannot unlock wallet: %s\n", strUnlockError.c_str());
388 DBFlush(true);
389 return 1;
390 }
391 }
392
302 // Before the node opens sockets or touches anything: the wallet is loaded, 393 // Before the node opens sockets or touches anything: the wallet is loaded,
303 // which is all a backup needs, and finishing here means the copy is taken 394 // which is all a backup needs, and finishing here means the copy is taken
304 // from a quiet directory rather than from under a running node. 395 // from a quiet directory rather than from under a running node.
@@ -442,6 +533,14 @@ int main(int argc, char* argv[])
442 return 0; 533 return 0;
443 } 534 }
444 535
536 if (fGenerateBitcoins && IsWalletLocked())
537 {
538 fprintf(stderr, "Cannot mine while the encrypted wallet is locked. "
539 "Start with /walletpassphrase or /walletpassphrase=@FILE, or disable mining.\n");
540 DBFlush(true);
541 return 1;
542 }
543
445 ReacceptWalletTransactions(); 544 ReacceptWalletTransactions();
446 545
447 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } 546 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; }
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 9bbfef1..e076c49 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -111,5 +111,8 @@ tests: test_bip32.exe bitflash.exe
111 ./test_bip32.exe 111 ./test_bip32.exe
112 ./bitflash.exe -selftest=wallet-keypool 112 ./bitflash.exe -selftest=wallet-keypool
113 ./bitflash.exe -selftest=wallet-hd 113 ./bitflash.exe -selftest=wallet-hd
114 ./bitflash.exe -selftest=wallet-format
115 ./bitflash.exe -selftest=wallet-crypto
116 ./bitflash.exe -selftest=wallet-encrypt
114 ./bitflash.exe -selftest=net-message 117 ./bitflash.exe -selftest=net-message
115 ./bitflash.exe -selftest=consensus-limits 118 ./bitflash.exe -selftest=consensus-limits
diff --git a/src/script.cpp b/src/script.cpp
index 6d4fb34..1e6e1ca 100644
--- a/src/script.cpp
+++ b/src/script.cpp
@@ -1020,43 +1020,54 @@ bool Solver(const CScript& scriptPubKey, uint256 hash, int nHashType, CScript& s
1020 if (!Solver(scriptPubKey, vSolution)) 1020 if (!Solver(scriptPubKey, vSolution))
1021 return false; 1021 return false;
1022 1022
1023 // Compile solution 1023 // Compile solution. Public keys stay available for encrypted wallets so the
1024 CRITICAL_BLOCK(cs_mapKeys) 1024 // wallet can recognize its own outputs while locked; private keys are
1025 // fetched only for an actual signature.
1026 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
1025 { 1027 {
1026 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) 1028 if (item.first == OP_PUBKEY)
1027 { 1029 {
1028 if (item.first == OP_PUBKEY) 1030 const valtype& vchPubKey = item.second;
1031 if (!WalletCanSpendKey(vchPubKey))
1032 return false;
1033 if (hash != 0)
1029 { 1034 {
1030 // Sign 1035 CPrivKey vchPrivKey;
1031 const valtype& vchPubKey = item.second; 1036 string strError;
1032 if (!mapKeys.count(vchPubKey)) 1037 if (!GetWalletPrivKey(vchPubKey, vchPrivKey, strError))
1038 return false;
1039 vector<unsigned char> vchSig;
1040 if (!CKey::Sign(vchPrivKey, hash, vchSig))
1033 return false; 1041 return false;
1034 if (hash != 0) 1042 vchSig.push_back((unsigned char)nHashType);
1043 scriptSigRet << vchSig;
1044 }
1045 }
1046 else if (item.first == OP_PUBKEYHASH)
1047 {
1048 vector<unsigned char> vchPubKey;
1049 {
1050 CRITICAL_BLOCK(cs_mapKeys)
1035 { 1051 {
1036 vector<unsigned char> vchSig; 1052 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));
1037 if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig)) 1053 if (mi == mapPubKeys.end())
1038 return false; 1054 return false;
1039 vchSig.push_back((unsigned char)nHashType); 1055 vchPubKey = (*mi).second;
1040 scriptSigRet << vchSig;
1041 } 1056 }
1042 } 1057 }
1043 else if (item.first == OP_PUBKEYHASH) 1058 if (!WalletCanSpendKey(vchPubKey))
1059 return false;
1060 if (hash != 0)
1044 { 1061 {
1045 // Sign and give pubkey 1062 CPrivKey vchPrivKey;
1046 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second)); 1063 string strError;
1047 if (mi == mapPubKeys.end()) 1064 if (!GetWalletPrivKey(vchPubKey, vchPrivKey, strError))
1048 return false; 1065 return false;
1049 const vector<unsigned char>& vchPubKey = (*mi).second; 1066 vector<unsigned char> vchSig;
1050 if (!mapKeys.count(vchPubKey)) 1067 if (!CKey::Sign(vchPrivKey, hash, vchSig))
1051 return false; 1068 return false;
1052 if (hash != 0) 1069 vchSig.push_back((unsigned char)nHashType);
1053 { 1070 scriptSigRet << vchSig << vchPubKey;
1054 vector<unsigned char> vchSig;
1055 if (!CKey::Sign(mapKeys[vchPubKey], hash, vchSig))
1056 return false;
1057 vchSig.push_back((unsigned char)nHashType);
1058 scriptSigRet << vchSig << vchPubKey;
1059 }
1060 } 1071 }
1061 } 1072 }
1062 } 1073 }
@@ -1080,27 +1091,27 @@ bool ExtractPubKey(const CScript& scriptPubKey, bool fMineOnly, vector<unsigned
1080 if (!Solver(scriptPubKey, vSolution)) 1091 if (!Solver(scriptPubKey, vSolution))
1081 return false; 1092 return false;
1082 1093
1083 CRITICAL_BLOCK(cs_mapKeys) 1094 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution)
1084 { 1095 {
1085 foreach(PAIRTYPE(opcodetype, valtype)& item, vSolution) 1096 valtype vchPubKey;
1097 if (item.first == OP_PUBKEY)
1086 { 1098 {
1087 valtype vchPubKey; 1099 vchPubKey = item.second;
1088 if (item.first == OP_PUBKEY) 1100 }
1089 { 1101 else if (item.first == OP_PUBKEYHASH)
1090 vchPubKey = item.second; 1102 {
1091 } 1103 CRITICAL_BLOCK(cs_mapKeys)
1092 else if (item.first == OP_PUBKEYHASH)
1093 { 1104 {
1094 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second)); 1105 map<uint160, valtype>::iterator mi = mapPubKeys.find(uint160(item.second));
1095 if (mi == mapPubKeys.end()) 1106 if (mi == mapPubKeys.end())
1096 continue; 1107 continue;
1097 vchPubKey = (*mi).second; 1108 vchPubKey = (*mi).second;
1098 } 1109 }
1099 if (!fMineOnly || mapKeys.count(vchPubKey)) 1110 }
1100 { 1111 if (!fMineOnly || WalletCanSpendKey(vchPubKey))
1101 vchPubKeyRet = vchPubKey; 1112 {
1102 return true; 1113 vchPubKeyRet = vchPubKey;
1103 } 1114 return true;
1104 } 1115 }
1105 } 1116 }
1106 return false; 1117 return false;
diff --git a/src/selftest.cpp b/src/selftest.cpp
index aee2951..ff0289b 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -33,6 +33,8 @@
33 33
34#ifdef _WIN32 34#ifdef _WIN32
35#include <direct.h> 35#include <direct.h>
36#include <fcntl.h>
37#include <io.h>
36#else 38#else
37#include <dirent.h> 39#include <dirent.h>
38#include <sys/stat.h> 40#include <sys/stat.h>
@@ -601,6 +603,500 @@ static int RunWalletHDSelfTest()
601 return nFail == 0 ? 0 : 1; 603 return nFail == 0 ? 0 : 1;
602} 604}
603 605
606static int RunWalletFormatSelfTest()
607{
608 fflush(stdout);
609 printf("wallet-format self-test\n");
610
611 std::string tmp;
612 std::string cwd;
613 if (!MakeTempDir(tmp))
614 {
615 printf(" FAIL could not create a temporary data directory\n");
616 return 1;
617 }
618 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
619 {
620 printf(" FAIL could not move into the temporary data directory\n");
621 RemoveTree(tmp);
622 return 1;
623 }
624
625 int nFail = 0;
626 strSetDataDir = tmp;
627 printf(" temp datadir: %s\n", tmp.c_str());
628
629 try
630 {
631 class CWalletDBRaw : public CWalletDB
632 {
633 public:
634 CWalletDBRaw(const char* pszMode="r+") : CWalletDB(pszMode) { }
635 bool WriteStringRecord(const string& strType, int nValue)
636 {
637 return Write(strType, nValue);
638 }
639 bool EraseStringRecord(const string& strType)
640 {
641 return Erase(strType);
642 }
643 };
644
645 if (!LoadWallet())
646 throw std::runtime_error("LoadWallet failed");
647
648 {
649 CWalletDB walletdb;
650 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_SUPPORTED),
651 "the current wallet format marker can be written") ? 0 : 1;
652 }
653
654 std::vector<unsigned char> vchDefaultKey;
655 nFail += Check(CWalletDB("r").LoadWallet(vchDefaultKey),
656 "the current wallet format marker loads") ? 0 : 1;
657
658 {
659 CWalletDBRaw walletdb;
660 nFail += Check(walletdb.WriteStringRecord("mkey", 1),
661 "a malformed encrypted master-key record can be written") ? 0 : 1;
662 }
663
664 vchDefaultKey.clear();
665 nFail += Check(!CWalletDB("r").LoadWallet(vchDefaultKey),
666 "a malformed encrypted master-key record is refused") ? 0 : 1;
667
668 {
669 CWalletDBRaw walletdb;
670 nFail += Check(walletdb.EraseStringRecord("mkey"),
671 "the malformed encrypted master-key record can be removed") ? 0 : 1;
672 }
673
674 {
675 CWalletDB walletdb;
676 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_SUPPORTED + 1),
677 "a future wallet format marker can be written") ? 0 : 1;
678 }
679
680 vchDefaultKey.clear();
681 nFail += Check(!CWalletDB("r").LoadWallet(vchDefaultKey),
682 "a future wallet format marker is refused") ? 0 : 1;
683 }
684 catch (const std::exception& e)
685 {
686 printf(" FAIL exception: %s\n", e.what());
687 nFail++;
688 }
689 catch (...)
690 {
691 printf(" FAIL unknown exception\n");
692 nFail++;
693 }
694
695 DBFlush(true);
696 SetCurrentDir(cwd);
697 RemoveTree(tmp);
698 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
699 nFail, nFail == 1 ? "" : "s");
700 fflush(stdout);
701 return nFail == 0 ? 0 : 1;
702}
703
704static void ClearWalletRuntimeForTest()
705{
706 CRITICAL_BLOCK(cs_mapKeys)
707 {
708 mapKeys.clear();
709 mapCryptedKeys.clear();
710 mapPubKeys.clear();
711 mapMasterKeys.clear();
712 vWalletMasterKey.clear();
713 vchCryptedHDMaster.clear();
714 vchCryptedHDChainCode.clear();
715 fWalletEncrypted = false;
716 fWalletLocked = true;
717 }
718 CRITICAL_BLOCK(cs_keyPool)
719 mapKeyPool.clear();
720 vchHDMaster.clear();
721 vchHDChainCode.clear();
722 nHDNext = 0;
723}
724
725static int RunWalletCryptoSelfTest()
726{
727 fflush(stdout);
728 printf("wallet-crypto self-test\n");
729
730 std::string tmp;
731 std::string cwd;
732 if (!MakeTempDir(tmp))
733 {
734 printf(" FAIL could not create a temporary data directory\n");
735 return 1;
736 }
737 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
738 {
739 printf(" FAIL could not move into the temporary data directory\n");
740 RemoveTree(tmp);
741 return 1;
742 }
743
744 int nFail = 0;
745 strSetDataDir = tmp;
746 printf(" temp datadir: %s\n", tmp.c_str());
747
748 try
749 {
750 const string strPassphrase = "btf-test-passphrase";
751 const string strWrongPassphrase = "btf-test-passphraser";
752
753 CKey key;
754 key.MakeNewKey();
755 vector<unsigned char> vchPubKey = key.GetPubKey();
756 CPrivKey vchPrivKey = key.GetPrivKey();
757
758 CKeyingMaterial vchMasterKey;
759 for (int i = 0; i < 32; i++)
760 vchMasterKey.push_back((unsigned char)(i + 1));
761
762 CWalletMasterKey kMasterKey;
763 kMasterKey.vchSalt.clear();
764 for (int i = 0; i < 8; i++)
765 kMasterKey.vchSalt.push_back((unsigned char)(0xa0 + i));
766 kMasterKey.nDeriveIterations = 2500;
767
768 CKeyingMaterial vchPassKey;
769 vector<unsigned char> vchPassIV;
770 nFail += Check(DeriveWalletPassphraseKey(strPassphrase, kMasterKey.vchSalt,
771 kMasterKey.nDeriveIterations,
772 vchPassKey, vchPassIV),
773 "a passphrase key can be derived") ? 0 : 1;
774 nFail += Check(EncryptSecret(vchPassKey,
775 vector<unsigned char>(vchMasterKey.begin(), vchMasterKey.end()),
776 vchPassIV, kMasterKey.vchCryptedKey),
777 "the wallet master key can be encrypted") ? 0 : 1;
778
779 vector<unsigned char> vchCryptedKey;
780 nFail += Check(EncryptSecret(vchMasterKey,
781 vector<unsigned char>(vchPrivKey.begin(), vchPrivKey.end()),
782 WalletKeyIV(vchPubKey), vchCryptedKey),
783 "a private key can be encrypted under the wallet master key") ? 0 : 1;
784
785 {
786 CWalletDB walletdb("cr");
787 nFail += Check(walletdb.WriteWalletMinVersion(WALLET_FORMAT_ENCRYPTED),
788 "an encrypted wallet format marker can be written") ? 0 : 1;
789 nFail += Check(walletdb.WriteMasterKey(0, kMasterKey),
790 "the encrypted master key can be written") ? 0 : 1;
791 nFail += Check(walletdb.WriteCryptedKey(vchPubKey, vchCryptedKey),
792 "the encrypted private key can be written") ? 0 : 1;
793 nFail += Check(walletdb.WriteDefaultKey(vchPubKey),
794 "the encrypted wallet default key can be written") ? 0 : 1;
795 }
796
797 ClearWalletRuntimeForTest();
798
799 vector<unsigned char> vchDefaultKey;
800 nFail += Check(CWalletDB("r").LoadWallet(vchDefaultKey),
801 "an encrypted wallet can be loaded") ? 0 : 1;
802 nFail += Check(vchDefaultKey == vchPubKey,
803 "the encrypted wallet default public key loads") ? 0 : 1;
804 nFail += Check(IsWalletEncrypted() && IsWalletLocked(),
805 "an encrypted wallet loads locked") ? 0 : 1;
806 nFail += Check(WalletCanSpendKey(vchPubKey),
807 "an encrypted key is recognized as wallet-owned while locked") ? 0 : 1;
808
809 CPrivKey vchDecrypted;
810 string strError;
811 nFail += Check(!GetWalletPrivKey(vchPubKey, vchDecrypted, strError),
812 "a locked encrypted key cannot be read") ? 0 : 1;
813 nFail += Check(!UnlockWallet(strWrongPassphrase, strError),
814 "the wrong-passphrase does not unlock") ? 0 : 1;
815 nFail += Check(UnlockWallet(strPassphrase, strError),
816 "the right passphrase unlocks") ? 0 : 1;
817 nFail += Check(!IsWalletLocked(),
818 "the wallet reports unlocked") ? 0 : 1;
819 nFail += Check(GetWalletPrivKey(vchPubKey, vchDecrypted, strError) &&
820 vchDecrypted == vchPrivKey,
821 "an unlocked encrypted key decrypts to the original private key") ? 0 : 1;
822 LockWallet();
823 nFail += Check(IsWalletLocked(),
824 "locking clears the unlocked state") ? 0 : 1;
825 }
826 catch (const std::exception& e)
827 {
828 printf(" FAIL exception: %s\n", e.what());
829 nFail++;
830 }
831 catch (...)
832 {
833 printf(" FAIL unknown exception\n");
834 nFail++;
835 }
836
837 DBFlush(true);
838 SetCurrentDir(cwd);
839 RemoveTree(tmp);
840 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
841 nFail, nFail == 1 ? "" : "s");
842 fflush(stdout);
843 return nFail == 0 ? 0 : 1;
844}
845
846static bool FileContainsBytes(const string& strPath, const vector<unsigned char>& vchNeedle)
847{
848 if (vchNeedle.empty())
849 return false;
850 FILE* pf = fopen(strPath.c_str(), "rb");
851 if (!pf)
852 return false;
853
854 vector<unsigned char> vchHaystack;
855 unsigned char buf[4096];
856 size_t n = 0;
857 while ((n = fread(buf, 1, sizeof(buf), pf)) > 0)
858 vchHaystack.insert(vchHaystack.end(), buf, buf + n);
859 fclose(pf);
860
861 if (vchHaystack.size() < vchNeedle.size())
862 return false;
863 return search(vchHaystack.begin(), vchHaystack.end(),
864 vchNeedle.begin(), vchNeedle.end()) != vchHaystack.end();
865}
866
867static bool FileContainsText(const string& strPath, const string& strNeedle)
868{
869 FILE* pf = fopen(strPath.c_str(), "rb");
870 if (!pf)
871 return false;
872
873 string strHaystack;
874 char buf[4096];
875 size_t n = 0;
876 while ((n = fread(buf, 1, sizeof(buf), pf)) > 0)
877 strHaystack.append(buf, buf + n);
878 fclose(pf);
879
880 return strHaystack.find(strNeedle) != string::npos;
881}
882
883static bool WriteTextFile(const string& strPath, const string& strText)
884{
885 FILE* pf = fopen(strPath.c_str(), "wb");
886 if (!pf)
887 return false;
888 bool fOk = fwrite(strText.data(), 1, strText.size(), pf) == strText.size();
889 if (fclose(pf) != 0)
890 fOk = false;
891 return fOk;
892}
893
894static string QuoteCommandArg(const string& str)
895{
896 string out = "\"";
897 for (size_t i = 0; i < str.size(); i++)
898 {
899 if (str[i] == '"')
900 out += "\\\"";
901 else
902 out += str[i];
903 }
904 out += "\"";
905 return out;
906}
907
908static int RunBitflashChild(const string& strExe,
909 const vector<string>& vArgs,
910 const string* pStdinFile = NULL)
911{
912#ifdef _WIN32
913 vector<char*> argv;
914 argv.push_back((char*)strExe.c_str());
915 for (size_t i = 0; i < vArgs.size(); i++)
916 argv.push_back((char*)vArgs[i].c_str());
917 argv.push_back(NULL);
918
919 int nOldOut = _dup(1);
920 int nOldErr = _dup(2);
921 int nOldIn = _dup(0);
922 int nNull = _open("NUL", _O_WRONLY);
923 int nIn = -1;
924 if (nNull >= 0)
925 {
926 _dup2(nNull, 1);
927 _dup2(nNull, 2);
928 }
929 if (pStdinFile)
930 {
931 nIn = _open(pStdinFile->c_str(), _O_RDONLY);
932 if (nIn >= 0)
933 _dup2(nIn, 0);
934 }
935 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
936 if (nIn >= 0)
937 _close(nIn);
938 if (nNull >= 0)
939 _close(nNull);
940 if (nOldIn >= 0)
941 {
942 _dup2(nOldIn, 0);
943 _close(nOldIn);
944 }
945 if (nOldOut >= 0)
946 {
947 _dup2(nOldOut, 1);
948 _close(nOldOut);
949 }
950 if (nOldErr >= 0)
951 {
952 _dup2(nOldErr, 2);
953 _close(nOldErr);
954 }
955 return nRet;
956#else
957 string strCmd = QuoteCommandArg(strExe);
958 for (size_t i = 0; i < vArgs.size(); i++)
959 strCmd += " " + QuoteCommandArg(vArgs[i]);
960 if (pStdinFile)
961 strCmd += " < " + QuoteCommandArg(*pStdinFile);
962 strCmd += " > /dev/null 2>&1";
963 return system(strCmd.c_str());
964#endif
965}
966
967static int RunWalletEncryptSelfTest()
968{
969 fflush(stdout);
970 printf("wallet-encrypt self-test\n");
971
972 std::string tmp;
973 std::string cwd;
974 if (!MakeTempDir(tmp))
975 {
976 printf(" FAIL could not create a temporary data directory\n");
977 return 1;
978 }
979 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
980 {
981 printf(" FAIL could not move into the temporary data directory\n");
982 RemoveTree(tmp);
983 return 1;
984 }
985
986 int nFail = 0;
987 bool fWalletEnvClosed = false;
988 strSetDataDir = tmp;
989 printf(" temp datadir: %s\n", tmp.c_str());
990
991 try
992 {
993 if (!LoadWallet())
994 throw std::runtime_error("LoadWallet failed");
995
996 string strError;
997 const string strMnemonic =
998 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
999 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
1000 "a phrase can be installed before encryption") ? 0 : 1;
1001 TopUpKeyPool();
1002
1003 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
1004 CPrivKey vchDefaultPrivKey;
1005 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
1006 "the default private key is readable before encryption") ? 0 : 1;
1007 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
1008 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
1009 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
1010
1011 string strBackup;
1012 nFail += Check(EncryptWallet("btf-test-passphrase", strBackup, strError),
1013 "wallet.dat can be rewritten encrypted") ? 0 : 1;
1014 fWalletEnvClosed = true;
1015 nFail += Check(FileExists(strBackup.c_str()),
1016 "the original wallet is preserved as an explicit backup") ? 0 : 1;
1017
1018 string strWalletPath = GetAppDir() + "/wallet.dat";
1019 nFail += Check(!FileContainsBytes(strWalletPath, vchDefaultPrivBytes),
1020 "the active wallet no longer contains the default private key bytes") ? 0 : 1;
1021 nFail += Check(!FileContainsBytes(strWalletPath, vchPlainHDMaster),
1022 "the active wallet no longer contains the HD master key bytes") ? 0 : 1;
1023 nFail += Check(!FileContainsBytes(strWalletPath, vchPlainHDChainCode),
1024 "the active wallet no longer contains the HD chain-code bytes") ? 0 : 1;
1025
1026#ifdef _WIN32
1027 string strExe = cwd + "\\bitflash.exe";
1028#else
1029 string strExe = cwd + "/bitflash-node";
1030#endif
1031 string strWrongDump = tmp + "/wrong-pass-dump.txt";
1032 string strRightDump = tmp + "/right-pass-dump.txt";
1033 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1034 string strStdinDump = tmp + "/stdin-pass-dump.txt";
1035 string strWrongPassFile = tmp + "/wrong-pass.txt";
1036 string strRightPassFile = tmp + "/right-pass.txt";
1037 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
1038 WriteTextFile(strRightPassFile, "btf-test-passphrase\n"),
1039 "passphrase files can be written for restarted commands") ? 0 : 1;
1040
1041 vector<string> vWrongArgs;
1042 vWrongArgs.push_back("-datadir=" + tmp);
1043 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
1044 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
1045 vWrongArgs.push_back("-nogui");
1046 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
1047 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1048 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1049
1050 vector<string> vLiteralArgs;
1051 vLiteralArgs.push_back("-datadir=" + tmp);
1052 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
1053 vLiteralArgs.push_back("-dumpwallet=" + strLiteralDump);
1054 vLiteralArgs.push_back("-nogui");
1055 int nLiteralRet = RunBitflashChild(strExe, vLiteralArgs);
1056 nFail += Check(nLiteralRet != 0 && !FileExists(strLiteralDump.c_str()),
1057 "literal passphrases on the command line are refused") ? 0 : 1;
1058
1059 vector<string> vRightArgs;
1060 vRightArgs.push_back("-datadir=" + tmp);
1061 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
1062 vRightArgs.push_back("-dumpwallet=" + strRightDump);
1063 vRightArgs.push_back("-nogui");
1064 int nRightRet = RunBitflashChild(strExe, vRightArgs);
1065 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
1066 "a restarted wallet unlocks with the right passphrase") ? 0 : 1;
1067 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1068 "the restarted wallet can decrypt and dump the original key") ? 0 : 1;
1069
1070 vector<string> vStdinArgs;
1071 vStdinArgs.push_back("-datadir=" + tmp);
1072 vStdinArgs.push_back("-walletpassphrase");
1073 vStdinArgs.push_back("-dumpwallet=" + strStdinDump);
1074 vStdinArgs.push_back("-nogui");
1075 int nStdinRet = RunBitflashChild(strExe, vStdinArgs, &strRightPassFile);
1076 nFail += Check(nStdinRet == 0 && FileExists(strStdinDump.c_str()),
1077 "a restarted wallet can read the passphrase from stdin") ? 0 : 1;
1078 }
1079 catch (const std::exception& e)
1080 {
1081 printf(" FAIL exception: %s\n", e.what());
1082 nFail++;
1083 }
1084 catch (...)
1085 {
1086 printf(" FAIL unknown exception\n");
1087 nFail++;
1088 }
1089
1090 if (!fWalletEnvClosed)
1091 DBFlush(true);
1092 SetCurrentDir(cwd);
1093 RemoveTree(tmp);
1094 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1095 nFail, nFail == 1 ? "" : "s");
1096 fflush(stdout);
1097 return nFail == 0 ? 0 : 1;
1098}
1099
604static int RunNetMessageSelfTest() 1100static int RunNetMessageSelfTest()
605{ 1101{
606 fflush(stdout); 1102 fflush(stdout);
@@ -728,12 +1224,18 @@ int RunSelfTest(const std::string& name)
728 return RunWalletKeyPoolSelfTest(); 1224 return RunWalletKeyPoolSelfTest();
729 if (name == "wallet-hd") 1225 if (name == "wallet-hd")
730 return RunWalletHDSelfTest(); 1226 return RunWalletHDSelfTest();
1227 if (name == "wallet-format")
1228 return RunWalletFormatSelfTest();
1229 if (name == "wallet-crypto")
1230 return RunWalletCryptoSelfTest();
1231 if (name == "wallet-encrypt")
1232 return RunWalletEncryptSelfTest();
731 if (name == "net-message") 1233 if (name == "net-message")
732 return RunNetMessageSelfTest(); 1234 return RunNetMessageSelfTest();
733 if (name == "consensus-limits") 1235 if (name == "consensus-limits")
734 return RunConsensusLimitsSelfTest(); 1236 return RunConsensusLimitsSelfTest();
735 1237
736 printf("Unknown self-test '%s'\n", name.c_str()); 1238 printf("Unknown self-test '%s'\n", name.c_str());
737 printf("Known self-tests: wallet-keypool, wallet-hd, net-message, consensus-limits\n"); 1239 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits\n");
738 return 1; 1240 return 1;
739} 1241}
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index b449df9..06f35a5 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -406,6 +406,36 @@ int CmdRecoveryAudit()
406 return 0; 406 return 0;
407} 407}
408 408
409int CmdEncryptWallet(const std::string& strPassphrase)
410{
411 AttachTerminal();
412
413 string strBackup;
414 string strError;
415 if (!EncryptWallet(strPassphrase, strBackup, strError))
416 {
417 fprintf(stderr, "Cannot encrypt wallet: %s\n", strError.c_str());
418 fprintf(stderr, "Nothing was changed.\n");
419 return 1;
420 }
421
422 printf("Wallet encrypted.\n");
423 printf("The old unencrypted wallet was moved to:\n");
424 printf(" %s\n", strBackup.c_str());
425 printf("That backup still contains private keys in plain text. Move it offline or\n");
426 printf("delete it after you have a safer backup plan.\n");
427 printf("Restart Bitflash before using the wallet again.\n");
428 fflush(stdout);
429
430 // The rewritten wallet.dat is clean; the environment's write-ahead logs are
431 // not, because every plaintext record this wallet ever wrote passed through
432 // them. Closing the environment and dropping the logs is the last step of
433 // encrypting, not an optimisation -- our own backup advice is to copy the
434 // whole directory, and following it otherwise carries the keys along.
435 PurgeDbEnvironmentLogs();
436 return 0;
437}
438
409// Spend, from the command line. 439// Spend, from the command line.
410// 440//
411// SendMoney() has been in this tree since 0.1.0 and only the window ever called 441// SendMoney() has been in this tree since 0.1.0 and only the window ever called
@@ -421,6 +451,12 @@ int CmdSendTo(const std::string& strArg)
421{ 451{
422 AttachTerminal(); 452 AttachTerminal();
423 453
454 if (IsWalletLocked())
455 {
456 fprintf(stderr, "Wallet is encrypted and locked. Start with /walletpassphrase or /walletpassphrase=@FILE to spend.\n");
457 return 1;
458 }
459
424 std::string::size_type comma = strArg.rfind(','); 460 std::string::size_type comma = strArg.rfind(',');
425 if (comma == std::string::npos) 461 if (comma == std::string::npos)
426 { 462 {
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 32d3eef..04d3fb7 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -53,6 +53,10 @@ int CmdShowDerived(int nCount);
53// by the installed recovery phrase or still depends on wallet.dat-only keys. 53// by the installed recovery phrase or still depends on wallet.dat-only keys.
54int CmdRecoveryAudit(); 54int CmdRecoveryAudit();
55 55
56// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
57// given passphrase. Returns 0 on success and exits without starting the node.
58int CmdEncryptWallet(const std::string& strPassphrase);
59
56// Spend, from the command line. Until now the only way to send was the window, 60// Spend, from the command line. Until now the only way to send was the window,
57// so a headless node could be paid and could never pay -- it could hold a 61// so a headless node could be paid and could never pay -- it could hold a
58// balance it had no way to move. strArg is "ADDRESS,AMOUNT". 62// balance it had no way to move. strArg is "ADDRESS,AMOUNT".