diff options
Diffstat (limited to 'src/db.cpp')
| -rw-r--r-- | src/db.cpp | 210 |
1 files changed, 207 insertions, 3 deletions
| @@ -1974,9 +1974,9 @@ static bool CopyPublicWalletRecordsTo(CWalletRewriteDB& dbTo, | |||
| 1974 | return ScanWalletRecords(visitor, strErrorRet); | 1974 | return ScanWalletRecords(visitor, strErrorRet); |
| 1975 | } | 1975 | } |
| 1976 | 1976 | ||
| 1977 | static string WalletEncryptBackupPath() | 1977 | static string WalletEncryptBackupPathFor(const char* pszBaseName) |
| 1978 | { | 1978 | { |
| 1979 | string strBase = GetAppDir() + "/wallet.dat.before-encrypt."; | 1979 | string strBase = GetAppDir() + "/" + pszBaseName + ".before-encrypt."; |
| 1980 | string strPath = strBase + i64tostr(GetTime()) + ".bak"; | 1980 | string strPath = strBase + i64tostr(GetTime()) + ".bak"; |
| 1981 | for (int i = 1; FileExists(strPath.c_str()); i++) | 1981 | for (int i = 1; FileExists(strPath.c_str()); i++) |
| 1982 | strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; | 1982 | strPath = strBase + i64tostr(GetTime()) + "." + itostr(i) + ".bak"; |
| @@ -2006,6 +2006,192 @@ static void SecureClearBytes(vector<unsigned char>& vch) | |||
| 2006 | vch.clear(); | 2006 | vch.clear(); |
| 2007 | } | 2007 | } |
| 2008 | 2008 | ||
| 2009 | // The SQLite counterpart of CWalletPublicRecordCopyVisitor: same skip list, | ||
| 2010 | // same byte-for-byte copy, but the destination is a CWalletDBSQLite record | ||
| 2011 | // store rather than a Berkeley DB file. | ||
| 2012 | class CWalletPublicRecordCopyToSQLiteVisitor : public CWalletRecordVisitor | ||
| 2013 | { | ||
| 2014 | public: | ||
| 2015 | CWalletDBSQLite& dbTo; | ||
| 2016 | int& nCopied; | ||
| 2017 | |||
| 2018 | CWalletPublicRecordCopyToSQLiteVisitor(CWalletDBSQLite& dbToIn, int& nCopiedIn) | ||
| 2019 | : dbTo(dbToIn), nCopied(nCopiedIn) { } | ||
| 2020 | |||
| 2021 | bool VisitWalletRecord(const CDataStream& ssKey, | ||
| 2022 | const CDataStream& ssValue, | ||
| 2023 | string& strErrorRet) | ||
| 2024 | { | ||
| 2025 | string strType; | ||
| 2026 | try | ||
| 2027 | { | ||
| 2028 | CDataStream ssType = ssKey; | ||
| 2029 | ssType >> strType; | ||
| 2030 | } | ||
| 2031 | catch (...) | ||
| 2032 | { | ||
| 2033 | strErrorRet = "SQLite wallet has a record whose key type cannot be read"; | ||
| 2034 | return false; | ||
| 2035 | } | ||
| 2036 | |||
| 2037 | if (IsWalletEncryptionPrivateRecord(strType)) | ||
| 2038 | return true; | ||
| 2039 | |||
| 2040 | vector<unsigned char> vchKey(ssKey.begin(), ssKey.end()); | ||
| 2041 | vector<unsigned char> vchValue(ssValue.begin(), ssValue.end()); | ||
| 2042 | bool fOk = dbTo.WriteRecord(vchKey, vchValue, strErrorRet, true); | ||
| 2043 | if (!vchKey.empty()) | ||
| 2044 | memset(&vchKey[0], 0, vchKey.size()); | ||
| 2045 | if (!vchValue.empty()) | ||
| 2046 | memset(&vchValue[0], 0, vchValue.size()); | ||
| 2047 | if (!fOk) | ||
| 2048 | { | ||
| 2049 | strErrorRet = strprintf("could not copy wallet record '%s': %s", | ||
| 2050 | strType.c_str(), strErrorRet.c_str()); | ||
| 2051 | return false; | ||
| 2052 | } | ||
| 2053 | nCopied++; | ||
| 2054 | return true; | ||
| 2055 | } | ||
| 2056 | }; | ||
| 2057 | |||
| 2058 | // Persist an encrypted wallet under the SQLite backend. Everything the caller | ||
| 2059 | // computed -- the master key, the re-encrypted private keys, the fresh crypted | ||
| 2060 | // key pool, the encrypted HD seed -- lands in a brand new wallet.sqlite built | ||
| 2061 | // in one transaction, which is then swapped in for the plaintext original. | ||
| 2062 | // | ||
| 2063 | // A fresh file rather than an in-place rewrite for two reasons: it keeps the | ||
| 2064 | // whole change atomic (a crash before the rename leaves the old wallet whole), | ||
| 2065 | // and it guarantees no plaintext key survives in a freed page the way an | ||
| 2066 | // in-place DELETE would leave behind until a VACUUM. | ||
| 2067 | static bool EncryptWalletPersistSQLite( | ||
| 2068 | const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vCryptedKeys, | ||
| 2069 | const vector<pair<vector<unsigned char>, vector<unsigned char> > >& vNewPoolKeys, | ||
| 2070 | const vector<pair<int64, vector<unsigned char> > >& vNewPool, | ||
| 2071 | const CWalletMasterKey& kMasterKey, | ||
| 2072 | const vector<unsigned char>& vchCryptedHDMasterNew, | ||
| 2073 | const vector<unsigned char>& vchCryptedHDChainCodeNew, | ||
| 2074 | unsigned int nHDNextNew, | ||
| 2075 | int& nCopiedRet, | ||
| 2076 | string& strBackupRet, | ||
| 2077 | string& strErrorRet) | ||
| 2078 | { | ||
| 2079 | nCopiedRet = 0; | ||
| 2080 | if (!pWalletSQLiteRuntime) | ||
| 2081 | { | ||
| 2082 | strErrorRet = "SQLite wallet backend is not active"; | ||
| 2083 | return false; | ||
| 2084 | } | ||
| 2085 | |||
| 2086 | string strWalletPath = GetAppDir() + "/wallet.sqlite"; | ||
| 2087 | string strTempPath = GetAppDir() + "/" + | ||
| 2088 | strprintf("wallet.sqlite.encrypting.%lld", GetTime()); | ||
| 2089 | remove(strTempPath.c_str()); | ||
| 2090 | remove((strTempPath + "-wal").c_str()); | ||
| 2091 | remove((strTempPath + "-shm").c_str()); | ||
| 2092 | |||
| 2093 | { | ||
| 2094 | CWalletDBSQLite dbTmp; | ||
| 2095 | if (!dbTmp.Open(strTempPath, strErrorRet)) | ||
| 2096 | { | ||
| 2097 | remove(strTempPath.c_str()); | ||
| 2098 | return false; | ||
| 2099 | } | ||
| 2100 | if (!dbTmp.BeginTransaction(strErrorRet)) | ||
| 2101 | { | ||
| 2102 | dbTmp.Close(); | ||
| 2103 | remove(strTempPath.c_str()); | ||
| 2104 | return false; | ||
| 2105 | } | ||
| 2106 | |||
| 2107 | // The public records come from the live SQLite store -- under this | ||
| 2108 | // backend ScanWalletRecords() has no Berkeley DB behind it to read. | ||
| 2109 | CWalletPublicRecordCopyToSQLiteVisitor visitor(dbTmp, nCopiedRet); | ||
| 2110 | bool fOk = pWalletSQLiteRuntime->ScanRecords(visitor, strErrorRet); | ||
| 2111 | |||
| 2112 | // Encrypted metadata, serialized byte-for-byte the way the Berkeley DB | ||
| 2113 | // path (CWalletRewriteDB) writes it, so a wallet encrypted under either | ||
| 2114 | // backend is the same set of records. ckey uses INSERT-only to match | ||
| 2115 | // WriteCryptedKey; the rest overwrite. | ||
| 2116 | fOk = fOk && | ||
| 2117 | dbTmp.WriteTypedRecord(string("walletminversion"), | ||
| 2118 | (int)WALLET_FORMAT_ENCRYPTED, strErrorRet, true) && | ||
| 2119 | dbTmp.WriteTypedRecord(make_pair(string("mkey"), | ||
| 2120 | (unsigned int)(nWalletMasterKeyMaxID + 1)), | ||
| 2121 | kMasterKey, strErrorRet, true); | ||
| 2122 | for (size_t i = 0; fOk && i < vCryptedKeys.size(); i++) | ||
| 2123 | fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vCryptedKeys[i].first), | ||
| 2124 | vCryptedKeys[i].second, strErrorRet, false); | ||
| 2125 | for (size_t i = 0; fOk && i < vNewPoolKeys.size(); i++) | ||
| 2126 | fOk = dbTmp.WriteTypedRecord(make_pair(string("ckey"), vNewPoolKeys[i].first), | ||
| 2127 | vNewPoolKeys[i].second, strErrorRet, false); | ||
| 2128 | if (fOk && !vchCryptedHDMasterNew.empty()) | ||
| 2129 | fOk = dbTmp.WriteTypedRecord(string("cryptedhdmaster"), | ||
| 2130 | vchCryptedHDMasterNew, strErrorRet, true) && | ||
| 2131 | dbTmp.WriteTypedRecord(string("cryptedhdchaincode"), | ||
| 2132 | vchCryptedHDChainCodeNew, strErrorRet, true); | ||
| 2133 | if (fOk) | ||
| 2134 | fOk = dbTmp.WriteTypedRecord(string("hdnext"), nHDNextNew, strErrorRet, true); | ||
| 2135 | for (size_t i = 0; fOk && i < vNewPool.size(); i++) | ||
| 2136 | fOk = dbTmp.WriteTypedRecord(make_pair(string("pool"), vNewPool[i].first), | ||
| 2137 | vNewPool[i].second, strErrorRet, true); | ||
| 2138 | |||
| 2139 | if (!fOk) | ||
| 2140 | { | ||
| 2141 | string strIgnore; | ||
| 2142 | dbTmp.RollbackTransaction(strIgnore); | ||
| 2143 | dbTmp.Close(); | ||
| 2144 | remove(strTempPath.c_str()); | ||
| 2145 | remove((strTempPath + "-wal").c_str()); | ||
| 2146 | remove((strTempPath + "-shm").c_str()); | ||
| 2147 | return false; | ||
| 2148 | } | ||
| 2149 | |||
| 2150 | if (!dbTmp.CommitTransaction(strErrorRet)) | ||
| 2151 | { | ||
| 2152 | dbTmp.Close(); | ||
| 2153 | remove(strTempPath.c_str()); | ||
| 2154 | remove((strTempPath + "-wal").c_str()); | ||
| 2155 | remove((strTempPath + "-shm").c_str()); | ||
| 2156 | return false; | ||
| 2157 | } | ||
| 2158 | string strCheckpointError; | ||
| 2159 | dbTmp.Checkpoint(strCheckpointError); | ||
| 2160 | dbTmp.Close(); | ||
| 2161 | } | ||
| 2162 | |||
| 2163 | // The rebuilt wallet is committed and self-contained. Close the live handle | ||
| 2164 | // so the file can be swapped: Windows will not rename a file that still has | ||
| 2165 | // an open handle, and the node exits right after this. | ||
| 2166 | WalletSQLiteRuntimeClose(); | ||
| 2167 | |||
| 2168 | string strBackupPath = WalletEncryptBackupPathFor("wallet.sqlite"); | ||
| 2169 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) | ||
| 2170 | { | ||
| 2171 | strErrorRet = strprintf("could not move the original SQLite wallet to %s", | ||
| 2172 | strBackupPath.c_str()); | ||
| 2173 | remove(strTempPath.c_str()); | ||
| 2174 | return false; | ||
| 2175 | } | ||
| 2176 | // The moved-aside wallet owns any leftover write-ahead log or shared-memory | ||
| 2177 | // sidecar; drop stale ones so they cannot shadow the new wallet. | ||
| 2178 | remove((strWalletPath + "-wal").c_str()); | ||
| 2179 | remove((strWalletPath + "-shm").c_str()); | ||
| 2180 | |||
| 2181 | if (rename(strTempPath.c_str(), strWalletPath.c_str()) != 0) | ||
| 2182 | { | ||
| 2183 | rename(strBackupPath.c_str(), strWalletPath.c_str()); | ||
| 2184 | strErrorRet = "could not install the encrypted SQLite wallet; the original was restored"; | ||
| 2185 | remove(strTempPath.c_str()); | ||
| 2186 | return false; | ||
| 2187 | } | ||
| 2188 | remove((strTempPath + "-wal").c_str()); | ||
| 2189 | remove((strTempPath + "-shm").c_str()); | ||
| 2190 | |||
| 2191 | strBackupRet = strBackupPath; | ||
| 2192 | return true; | ||
| 2193 | } | ||
| 2194 | |||
| 2009 | bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) | 2195 | bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) |
| 2010 | { | 2196 | { |
| 2011 | strBackupRet.clear(); | 2197 | strBackupRet.clear(); |
| @@ -2144,6 +2330,24 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st | |||
| 2144 | vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); | 2330 | vNewPool.push_back(make_pair((int64)i + 1, vchPubKey)); |
| 2145 | } | 2331 | } |
| 2146 | 2332 | ||
| 2333 | // Every ciphertext is computed; nothing has touched the store yet. Under | ||
| 2334 | // the SQLite backend, persist by rebuilding wallet.sqlite in one atomic | ||
| 2335 | // transaction and swapping it in. The Berkeley DB path below is unchanged. | ||
| 2336 | if (WalletSQLiteRuntimeActive()) | ||
| 2337 | { | ||
| 2338 | int nCopiedSQLite = 0; | ||
| 2339 | if (!EncryptWalletPersistSQLite(vCryptedKeys, vNewPoolKeys, vNewPool, | ||
| 2340 | kMasterKey, vchCryptedHDMasterNew, | ||
| 2341 | vchCryptedHDChainCodeNew, nHDNextNew, | ||
| 2342 | nCopiedSQLite, strBackupRet, strErrorRet)) | ||
| 2343 | return false; | ||
| 2344 | printf("EncryptWallet() : copied %d public record(s), encrypted %u key(s), " | ||
| 2345 | "rebuilt %d key-pool entry(s) [SQLite]\n", | ||
| 2346 | nCopiedSQLite, (unsigned int)(vCryptedKeys.size() + vNewPoolKeys.size()), | ||
| 2347 | (int)vNewPool.size()); | ||
| 2348 | return true; | ||
| 2349 | } | ||
| 2350 | |||
| 2147 | string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); | 2351 | string strTempFile = strprintf("wallet.encrypting.%lld.dat", GetTime()); |
| 2148 | string strTempPath = GetAppDir() + "/" + strTempFile; | 2352 | string strTempPath = GetAppDir() + "/" + strTempFile; |
| 2149 | remove(strTempPath.c_str()); | 2353 | remove(strTempPath.c_str()); |
| @@ -2211,7 +2415,7 @@ bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& st | |||
| 2211 | DBFlush(true); | 2415 | DBFlush(true); |
| 2212 | 2416 | ||
| 2213 | string strWalletPath = GetAppDir() + "/wallet.dat"; | 2417 | string strWalletPath = GetAppDir() + "/wallet.dat"; |
| 2214 | string strBackupPath = WalletEncryptBackupPath(); | 2418 | string strBackupPath = WalletEncryptBackupPathFor("wallet.dat"); |
| 2215 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) | 2419 | if (rename(strWalletPath.c_str(), strBackupPath.c_str()) != 0) |
| 2216 | { | 2420 | { |
| 2217 | strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); | 2421 | strErrorRet = strprintf("could not move the original wallet to %s", strBackupPath.c_str()); |