upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/db.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/db.cpp')
-rw-r--r--src/db.cpp210
1 files changed, 207 insertions, 3 deletions
diff --git a/src/db.cpp b/src/db.cpp
index 771a4ba..86f6dd7 100644
--- a/src/db.cpp
+++ b/src/db.cpp
@@ -1974,9 +1974,9 @@ static bool CopyPublicWalletRecordsTo(CWalletRewriteDB& dbTo,
1974 return ScanWalletRecords(visitor, strErrorRet); 1974 return ScanWalletRecords(visitor, strErrorRet);
1975} 1975}
1976 1976
1977static string WalletEncryptBackupPath() 1977static 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.
2012class CWalletPublicRecordCopyToSQLiteVisitor : public CWalletRecordVisitor
2013{
2014public:
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.
2067static 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
2009bool EncryptWallet(const string& strPassphrase, string& strBackupRet, string& strErrorRet) 2195bool 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());