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:
authorbitflash <dev@bitflash.network>2026-08-18 14:23:38 -0300
committerbitflash <dev@bitflash.network>2026-08-18 14:23:38 -0300
commite58c4022e0ddd137b93cdb3464753bef5df1d456 (patch)
tree8adc9ec855d19e5a7308eb2a1b2be8139db47188 /src
parentbeb5c1435a0d85b7b24d4d371933758ebed90907 (diff)
wallet: encrypt atomically under the SQLite backend
EncryptWallet() had only a Berkeley DB path: it rebuilt a wallet.dat temp file and swapped it in. Under -walletbackend=sqlite that was wrong -- it would leave the live wallet.sqlite unencrypted and write a divergent wallet.dat instead. Add a SQLite persistence branch that rebuilds a fresh wallet.sqlite in one transaction (public records copied from the live store, encrypted key material serialized byte-for-byte the way the BDB path writes it), checkpoints it, and swaps it in with a .before-encrypt backup, mirroring the BDB rename dance. A fresh file rather than an in-place rewrite keeps the change atomic and leaves no plaintext key in a freed page. CmdEncryptWallet skips PurgeDbEnvironmentLogs under SQLite (no Berkeley DB environment behind the wallet) and reports the backend it used. New wallet-sqlite-encrypt self-test drives the real command path in child processes: export a wallet to SQLite, encrypt it through -walletbackend=sqlite, and prove it comes out encrypted, unlockable with the right passphrase, refusing the wrong one, with its recovery phrase intact and the original wallet.dat left untouched.
Diffstat (limited to 'src')
-rw-r--r--src/db.cpp210
-rw-r--r--src/makefile.mingw1
-rw-r--r--src/selftest.cpp189
-rw-r--r--src/walletcmd.cpp15
4 files changed, 410 insertions, 5 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());
diff --git a/src/makefile.mingw b/src/makefile.mingw
index 14e6b32..1cb47fb 100644
--- a/src/makefile.mingw
+++ b/src/makefile.mingw
@@ -112,6 +112,7 @@ tests: test_bip32.exe bitflash.exe
112 ./bitflash.exe -selftest=wallet-sqlite-migration 112 ./bitflash.exe -selftest=wallet-sqlite-migration
113 ./bitflash.exe -selftest=wallet-crypto 113 ./bitflash.exe -selftest=wallet-crypto
114 ./bitflash.exe -selftest=wallet-encrypt 114 ./bitflash.exe -selftest=wallet-encrypt
115 ./bitflash.exe -selftest=wallet-sqlite-encrypt
115 ./bitflash.exe -selftest=net-message 116 ./bitflash.exe -selftest=net-message
116 ./bitflash.exe -selftest=consensus-limits 117 ./bitflash.exe -selftest=consensus-limits
117 ./bitflash.exe -selftest=pool-stratum 118 ./bitflash.exe -selftest=pool-stratum
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 098eed6..fc10947 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -2252,6 +2252,191 @@ static int RunWalletEncryptSelfTest()
2252 return nFail == 0 ? 0 : 1; 2252 return nFail == 0 ? 0 : 1;
2253} 2253}
2254 2254
2255// Encrypting a wallet while it runs on the SQLite backend. Unlike the Berkeley
2256// DB encrypt test above, this drives the real command-line path end to end in
2257// child processes: build a Berkeley DB wallet, export it to SQLite, then run
2258// -walletbackend=sqlite -encryptwallet and prove the SQLite wallet came out
2259// encrypted, unlockable, and with wallet.dat left untouched.
2260static int RunWalletSQLiteEncryptSelfTest()
2261{
2262 fflush(stdout);
2263 printf("wallet-sqlite-encrypt self-test\n");
2264
2265 std::string tmp;
2266 std::string cwd;
2267 if (!MakeTempDir(tmp))
2268 {
2269 printf(" FAIL could not create a temporary data directory\n");
2270 return 1;
2271 }
2272 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2273 {
2274 printf(" FAIL could not move into the temporary data directory\n");
2275 RemoveTree(tmp);
2276 return 1;
2277 }
2278
2279 int nFail = 0;
2280 strSetDataDir = tmp;
2281 printf(" temp datadir: %s\n", tmp.c_str());
2282
2283 try
2284 {
2285 // Build a normal Berkeley DB wallet with a recovery phrase, a key pool,
2286 // and a couple of public records, then release it. Everything after this
2287 // runs the real command-line paths in child processes.
2288 if (!LoadWallet())
2289 throw std::runtime_error("LoadWallet failed");
2290
2291 string strError;
2292 const string strMnemonic =
2293 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
2294 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
2295 "a phrase can be installed before encryption") ? 0 : 1;
2296 TopUpKeyPool();
2297
2298 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2299 CKey publicLabelKey;
2300 publicLabelKey.MakeNewKey();
2301 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2302 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2303 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2304 "public wallet records can be written before encryption") ? 0 : 1;
2305
2306 CPrivKey vchDefaultPrivKey;
2307 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2308 "the default private key is readable before encryption") ? 0 : 1;
2309 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
2310 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
2311 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
2312
2313 // Release wallet.dat so a child process can read it for the export.
2314 DBFlush(true);
2315
2316#ifdef _WIN32
2317 string strExe = cwd + "\\bitflash.exe";
2318#else
2319 string strExe = cwd + "/bitflash-node";
2320#endif
2321 string strSQLitePath = tmp + "/wallet.sqlite";
2322 string strWalletDatPath = tmp + "/wallet.dat";
2323 string strRightPassFile = tmp + "/right-pass.txt";
2324 string strWrongPassFile = tmp + "/wrong-pass.txt";
2325 nFail += Check(WriteTextFile(strRightPassFile, "btf-test-passphrase\n") &&
2326 WriteTextFile(strWrongPassFile, "wrong-passphrase\n"),
2327 "passphrase files can be written for restarted commands") ? 0 : 1;
2328
2329 // Step 1: export the Berkeley DB wallet to SQLite.
2330 vector<string> vExportArgs;
2331 vExportArgs.push_back("-datadir=" + tmp);
2332 vExportArgs.push_back("-walletsqliteexport=" + strSQLitePath);
2333 vExportArgs.push_back("-nogui");
2334 int nExportRet = RunBitflashChild(strExe, vExportArgs);
2335 nFail += Check(nExportRet == 0 && FileExists(strSQLitePath.c_str()),
2336 "the wallet exports to a SQLite file") ? 0 : 1;
2337 nFail += Check(FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2338 "the exported SQLite wallet holds the plaintext key before encryption") ? 0 : 1;
2339
2340 // Step 2: encrypt through the real SQLite backend command path.
2341 string strEncryptOut = tmp + "/sqlite-encrypt-out.txt";
2342 vector<string> vEncryptArgs;
2343 vEncryptArgs.push_back("-datadir=" + tmp);
2344 vEncryptArgs.push_back("-walletbackend=sqlite");
2345 vEncryptArgs.push_back("-encryptwallet=@" + strRightPassFile);
2346 vEncryptArgs.push_back("-nogui");
2347 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2348 nFail += Check(nEncryptRet == 0,
2349 "the SQLite wallet can be encrypted through -walletbackend=sqlite") ? 0 : 1;
2350 nFail += Check(FileContainsText(strEncryptOut, "Wallet backend: SQLite"),
2351 "encryption took the SQLite persistence path") ? 0 : 1;
2352 nFail += Check(FileContainsText(strEncryptOut, "wallet.sqlite.before-encrypt"),
2353 "the plaintext SQLite wallet is preserved as an explicit backup") ? 0 : 1;
2354
2355 // The encrypted SQLite wallet must no longer hold the plaintext secrets.
2356 nFail += Check(!FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2357 "the encrypted SQLite wallet no longer contains the default private key bytes") ? 0 : 1;
2358 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDMaster),
2359 "the encrypted SQLite wallet no longer contains the HD master key bytes") ? 0 : 1;
2360 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDChainCode),
2361 "the encrypted SQLite wallet no longer contains the HD chain-code bytes") ? 0 : 1;
2362
2363 // The Berkeley DB wallet.dat is a separate file: the SQLite encrypt path
2364 // must never touch it, so its plaintext key is still exactly there.
2365 nFail += Check(FileContainsBytes(strWalletDatPath, vchDefaultPrivBytes),
2366 "the original Berkeley DB wallet.dat is left untouched by the SQLite encrypt") ? 0 : 1;
2367
2368 // Step 3: a locked encrypted SQLite wallet reports that phrase coverage
2369 // needs an unlock, exactly as the Berkeley DB path does (exit code 2).
2370 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2371 vector<string> vLockedArgs;
2372 vLockedArgs.push_back("-datadir=" + tmp);
2373 vLockedArgs.push_back("-walletbackend=sqlite");
2374 vLockedArgs.push_back("-recoveryaudit");
2375 vLockedArgs.push_back("-nogui");
2376 int nLockedRet = RunBitflashChild(strExe, vLockedArgs, NULL, &strLockedAudit);
2377 nFail += Check(nLockedRet == 2 &&
2378 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit"),
2379 "a locked encrypted SQLite wallet reports that phrase coverage needs unlock") ? 0 : 1;
2380
2381 // The wrong passphrase is rejected and reveals nothing.
2382 string strWrongDump = tmp + "/wrong-dump.txt";
2383 vector<string> vWrongArgs;
2384 vWrongArgs.push_back("-datadir=" + tmp);
2385 vWrongArgs.push_back("-walletbackend=sqlite");
2386 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
2387 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
2388 vWrongArgs.push_back("-nogui");
2389 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
2390 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
2391 "a restarted SQLite wallet rejects the wrong passphrase") ? 0 : 1;
2392
2393 // The right passphrase unlocks and can dump the original key.
2394 string strRightDump = tmp + "/right-dump.txt";
2395 vector<string> vRightArgs;
2396 vRightArgs.push_back("-datadir=" + tmp);
2397 vRightArgs.push_back("-walletbackend=sqlite");
2398 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2399 vRightArgs.push_back("-dumpwallet=" + strRightDump);
2400 vRightArgs.push_back("-nogui");
2401 int nRightRet = RunBitflashChild(strExe, vRightArgs);
2402 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
2403 "a restarted SQLite wallet unlocks with the right passphrase") ? 0 : 1;
2404 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
2405 "the unlocked SQLite wallet decrypts and dumps the original key") ? 0 : 1;
2406
2407 // The recovery phrase survives the encryption.
2408 string strAudit = tmp + "/recovery-audit.txt";
2409 vector<string> vAuditArgs;
2410 vAuditArgs.push_back("-datadir=" + tmp);
2411 vAuditArgs.push_back("-walletbackend=sqlite");
2412 vAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2413 vAuditArgs.push_back("-recoveryaudit");
2414 vAuditArgs.push_back("-nogui");
2415 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2416 nFail += Check(nAuditRet == 0 &&
2417 FileContainsText(strAudit, "recovery phrase: present"),
2418 "the encrypted SQLite wallet still has its recovery phrase") ? 0 : 1;
2419 }
2420 catch (const std::exception& e)
2421 {
2422 printf(" FAIL exception: %s\n", e.what());
2423 nFail++;
2424 }
2425 catch (...)
2426 {
2427 printf(" FAIL unknown exception\n");
2428 nFail++;
2429 }
2430
2431 DBFlush(true);
2432 SetCurrentDir(cwd);
2433 RemoveTree(tmp);
2434 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2435 nFail, nFail == 1 ? "" : "s");
2436 fflush(stdout);
2437 return nFail == 0 ? 0 : 1;
2438}
2439
2255static int RunWalletPortabilitySelfTest() 2440static int RunWalletPortabilitySelfTest()
2256{ 2441{
2257 fflush(stdout); 2442 fflush(stdout);
@@ -2981,6 +3166,8 @@ int RunSelfTest(const std::string& name)
2981 return RunWalletCryptoSelfTest(); 3166 return RunWalletCryptoSelfTest();
2982 if (name == "wallet-encrypt") 3167 if (name == "wallet-encrypt")
2983 return RunWalletEncryptSelfTest(); 3168 return RunWalletEncryptSelfTest();
3169 if (name == "wallet-sqlite-encrypt")
3170 return RunWalletSQLiteEncryptSelfTest();
2984 if (name == "wallet-portability") 3171 if (name == "wallet-portability")
2985 return RunWalletPortabilitySelfTest(); 3172 return RunWalletPortabilitySelfTest();
2986 if (name == "net-message") 3173 if (name == "net-message")
@@ -2997,6 +3184,6 @@ int RunSelfTest(const std::string& name)
2997 return RunManagedTorSelfTest(); 3184 return RunManagedTorSelfTest();
2998 3185
2999 printf("Unknown self-test '%s'\n", name.c_str()); 3186 printf("Unknown self-test '%s'\n", name.c_str());
3000 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); 3187 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-storage-sanity, db-env-reopen, wallet-sqlite, wallet-sqlite-migration, wallet-crypto, wallet-encrypt, wallet-sqlite-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n");
3001 return 1; 3188 return 1;
3002} 3189}
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index 4dda8dd..374b2cc 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -1365,6 +1365,11 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1365{ 1365{
1366 AttachTerminal(); 1366 AttachTerminal();
1367 1367
1368 // Capture the backend now: under SQLite, EncryptWallet() closes the runtime
1369 // store to swap the file, so WalletSQLiteRuntimeActive() reads false by the
1370 // time we would decide whether to purge Berkeley DB logs below.
1371 bool fSQLiteBackend = WalletSQLiteRuntimeActive();
1372
1368 string strBackup; 1373 string strBackup;
1369 string strError; 1374 string strError;
1370 if (!EncryptWallet(strPassphrase, strBackup, strError)) 1375 if (!EncryptWallet(strPassphrase, strBackup, strError))
@@ -1375,6 +1380,8 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1375 } 1380 }
1376 1381
1377 printf("Wallet encrypted.\n"); 1382 printf("Wallet encrypted.\n");
1383 if (fSQLiteBackend)
1384 printf("Wallet backend: SQLite (wallet.sqlite).\n");
1378 printf("The old unencrypted wallet was moved to:\n"); 1385 printf("The old unencrypted wallet was moved to:\n");
1379 printf(" %s\n", strBackup.c_str()); 1386 printf(" %s\n", strBackup.c_str());
1380 printf("That backup still contains private keys in plain text. Move it offline or\n"); 1387 printf("That backup still contains private keys in plain text. Move it offline or\n");
@@ -1387,7 +1394,13 @@ int CmdEncryptWallet(const std::string& strPassphrase)
1387 // them. Closing the environment and dropping the logs is the last step of 1394 // them. Closing the environment and dropping the logs is the last step of
1388 // encrypting, not an optimisation -- our own backup advice is to copy the 1395 // encrypting, not an optimisation -- our own backup advice is to copy the
1389 // whole directory, and following it otherwise carries the keys along. 1396 // whole directory, and following it otherwise carries the keys along.
1390 PurgeDbEnvironmentLogs(); 1397 //
1398 // The SQLite backend has no Berkeley DB environment behind the wallet, and
1399 // EncryptWallet() already closed and swapped the SQLite store; there is
1400 // nothing to purge, and calling into the environment here would only open
1401 // one needlessly.
1402 if (!fSQLiteBackend)
1403 PurgeDbEnvironmentLogs();
1391 return 0; 1404 return 0;
1392} 1405}
1393 1406