diff options
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 602 |
1 files changed, 569 insertions, 33 deletions
| @@ -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. | ||
| 235 | void 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 | |||
| 206 | void DBFlush(bool fShutdown) | 256 | void 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 | |||
| 1256 | class CWalletRewriteDB : public CDB | ||
| 1257 | { | ||
| 1258 | public: | ||
| 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 | |||
| 1373 | static 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 | |||
| 1382 | static 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 | |||
| 1398 | bool 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 | } | ||