upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/selftest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/selftest.cpp')
-rw-r--r--src/selftest.cpp286
1 files changed, 285 insertions, 1 deletions
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 098eed6..90a30d2 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -1231,6 +1231,43 @@ static int RunWalletSQLiteSelfTest()
1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()), 1231 nFail += Check(!FileExists((strRuntimeDir + "/wallet.dat").c_str()),
1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1; 1232 "active SQLite runtime does not create wallet.dat") ? 0 : 1;
1233 1233
1234 // Transaction bracket: a rolled-back group leaves nothing, a
1235 // committed group persists. This is the all-or-nothing that
1236 // WriteHDMaster relies on so a half-written seed cannot survive
1237 // a failure between its two records.
1238 bool fTxnBegan = WalletSQLiteRuntimeBeginTxn();
1239 bool fTxnWrote = fTxnBegan &&
1240 CWalletDB().WriteSetting("runtime-txn-rollback", (int64)111);
1241 if (fTxnBegan)
1242 WalletSQLiteRuntimeRollbackTxn();
1243 nFail += Check(fTxnBegan && fTxnWrote,
1244 "a SQLite runtime transaction opens and rolls back") ? 0 : 1;
1245 int64 nRolledBack = 0;
1246 nFail += Check(!CWalletDB("r").ReadSetting("runtime-txn-rollback",
1247 nRolledBack),
1248 "a rolled-back SQLite runtime write leaves no record") ? 0 : 1;
1249
1250 bool fCommitOk = WalletSQLiteRuntimeBeginTxn() &&
1251 CWalletDB().WriteSetting("runtime-txn-commit", (int64)222) &&
1252 WalletSQLiteRuntimeCommitTxn();
1253 int64 nCommitted = 0;
1254 nFail += Check(fCommitOk &&
1255 CWalletDB("r").ReadSetting("runtime-txn-commit",
1256 nCommitted) &&
1257 nCommitted == 222,
1258 "a committed SQLite runtime write persists") ? 0 : 1;
1259
1260 // WriteHDMaster brackets its two records in that transaction;
1261 // both halves of the seed must land.
1262 vector<unsigned char> vchTxnMaster(32, 0x5a);
1263 vector<unsigned char> vchTxnChainCode(32, 0xa5);
1264 vector<unsigned char> vchReadMaster, vchReadChainCode;
1265 nFail += Check(CWalletDB().WriteHDMaster(vchTxnMaster, vchTxnChainCode) &&
1266 CWalletDB("r").ReadHDMaster(vchReadMaster, vchReadChainCode) &&
1267 vchReadMaster == vchTxnMaster &&
1268 vchReadChainCode == vchTxnChainCode,
1269 "WriteHDMaster commits both halves of the seed under SQLite") ? 0 : 1;
1270
1234 // Durability: close the backend (checkpoints the WAL), reopen 1271 // Durability: close the backend (checkpoints the WAL), reopen
1235 // the same file, and confirm what CWalletDB wrote is still 1272 // the same file, and confirm what CWalletDB wrote is still
1236 // there. This is the write-survives-a-restart property the real 1273 // there. This is the write-survives-a-restart property the real
@@ -2252,6 +2289,251 @@ static int RunWalletEncryptSelfTest()
2252 return nFail == 0 ? 0 : 1; 2289 return nFail == 0 ? 0 : 1;
2253} 2290}
2254 2291
2292// Encrypting a wallet while it runs on the SQLite backend. Unlike the Berkeley
2293// DB encrypt test above, this drives the real command-line path end to end in
2294// child processes: build a Berkeley DB wallet, export it to SQLite, then run
2295// -walletbackend=sqlite -encryptwallet and prove the SQLite wallet came out
2296// encrypted, unlockable, and with wallet.dat left untouched.
2297static int RunWalletSQLiteEncryptSelfTest()
2298{
2299 fflush(stdout);
2300 printf("wallet-sqlite-encrypt self-test\n");
2301
2302 std::string tmp;
2303 std::string cwd;
2304 if (!MakeTempDir(tmp))
2305 {
2306 printf(" FAIL could not create a temporary data directory\n");
2307 return 1;
2308 }
2309 if (!GetCurrentDir(cwd) || !SetCurrentDir(tmp))
2310 {
2311 printf(" FAIL could not move into the temporary data directory\n");
2312 RemoveTree(tmp);
2313 return 1;
2314 }
2315
2316 int nFail = 0;
2317 strSetDataDir = tmp;
2318 printf(" temp datadir: %s\n", tmp.c_str());
2319
2320 try
2321 {
2322 // Build a normal Berkeley DB wallet with a recovery phrase, a key pool,
2323 // and a couple of public records, then release it. Everything after this
2324 // runs the real command-line paths in child processes.
2325 if (!LoadWallet())
2326 throw std::runtime_error("LoadWallet failed");
2327
2328 string strError;
2329 const string strMnemonic =
2330 "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
2331 nFail += Check(SetHDSeedFromMnemonic(strMnemonic, strError),
2332 "a phrase can be installed before encryption") ? 0 : 1;
2333 TopUpKeyPool();
2334
2335 vector<unsigned char> vchDefaultPubKey = keyUser.GetPubKey();
2336 CKey publicLabelKey;
2337 publicLabelKey.MakeNewKey();
2338 string strPublicLabelAddress = PubKeyToAddress(publicLabelKey.GetPubKey());
2339 nFail += Check(CWalletDB().WriteName(strPublicLabelAddress, "rewrite-public-name") &&
2340 CWalletDB().WriteSetting("rewrite-public-setting", (int64)424242),
2341 "public wallet records can be written before encryption") ? 0 : 1;
2342
2343 CPrivKey vchDefaultPrivKey;
2344 nFail += Check(GetWalletPrivKey(vchDefaultPubKey, vchDefaultPrivKey, strError),
2345 "the default private key is readable before encryption") ? 0 : 1;
2346 vector<unsigned char> vchDefaultPrivBytes(vchDefaultPrivKey.begin(), vchDefaultPrivKey.end());
2347 vector<unsigned char> vchPlainHDMaster = vchHDMaster;
2348 vector<unsigned char> vchPlainHDChainCode = vchHDChainCode;
2349
2350 // Release wallet.dat so a child process can read it for the export.
2351 DBFlush(true);
2352
2353#ifdef _WIN32
2354 string strExe = cwd + "\\bitflash.exe";
2355#else
2356 string strExe = cwd + "/bitflash-node";
2357#endif
2358 string strSQLitePath = tmp + "/wallet.sqlite";
2359 string strWalletDatPath = tmp + "/wallet.dat";
2360 string strRightPassFile = tmp + "/right-pass.txt";
2361 string strWrongPassFile = tmp + "/wrong-pass.txt";
2362 nFail += Check(WriteTextFile(strRightPassFile, "btf-test-passphrase\n") &&
2363 WriteTextFile(strWrongPassFile, "wrong-passphrase\n"),
2364 "passphrase files can be written for restarted commands") ? 0 : 1;
2365
2366 // Step 1: export the Berkeley DB wallet to SQLite.
2367 vector<string> vExportArgs;
2368 vExportArgs.push_back("-datadir=" + tmp);
2369 vExportArgs.push_back("-walletsqliteexport=" + strSQLitePath);
2370 vExportArgs.push_back("-nogui");
2371 int nExportRet = RunBitflashChild(strExe, vExportArgs);
2372 nFail += Check(nExportRet == 0 && FileExists(strSQLitePath.c_str()),
2373 "the wallet exports to a SQLite file") ? 0 : 1;
2374 nFail += Check(FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2375 "the exported SQLite wallet holds the plaintext key before encryption") ? 0 : 1;
2376
2377 // Step 2: encrypt through the real SQLite backend command path.
2378 string strEncryptOut = tmp + "/sqlite-encrypt-out.txt";
2379 vector<string> vEncryptArgs;
2380 vEncryptArgs.push_back("-datadir=" + tmp);
2381 vEncryptArgs.push_back("-walletbackend=sqlite");
2382 vEncryptArgs.push_back("-encryptwallet=@" + strRightPassFile);
2383 vEncryptArgs.push_back("-nogui");
2384 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
2385 nFail += Check(nEncryptRet == 0,
2386 "the SQLite wallet can be encrypted through -walletbackend=sqlite") ? 0 : 1;
2387 nFail += Check(FileContainsText(strEncryptOut, "Wallet backend: SQLite"),
2388 "encryption took the SQLite persistence path") ? 0 : 1;
2389 nFail += Check(FileContainsText(strEncryptOut, "wallet.sqlite.before-encrypt"),
2390 "the plaintext SQLite wallet is preserved as an explicit backup") ? 0 : 1;
2391
2392 // The encrypted SQLite wallet must no longer hold the plaintext secrets.
2393 nFail += Check(!FileContainsBytes(strSQLitePath, vchDefaultPrivBytes),
2394 "the encrypted SQLite wallet no longer contains the default private key bytes") ? 0 : 1;
2395 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDMaster),
2396 "the encrypted SQLite wallet no longer contains the HD master key bytes") ? 0 : 1;
2397 nFail += Check(!FileContainsBytes(strSQLitePath, vchPlainHDChainCode),
2398 "the encrypted SQLite wallet no longer contains the HD chain-code bytes") ? 0 : 1;
2399
2400 // The Berkeley DB wallet.dat is a separate file: the SQLite encrypt path
2401 // must never touch it, so its plaintext key is still exactly there.
2402 nFail += Check(FileContainsBytes(strWalletDatPath, vchDefaultPrivBytes),
2403 "the original Berkeley DB wallet.dat is left untouched by the SQLite encrypt") ? 0 : 1;
2404
2405 // Step 3: a locked encrypted SQLite wallet reports that phrase coverage
2406 // needs an unlock, exactly as the Berkeley DB path does (exit code 2).
2407 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
2408 vector<string> vLockedArgs;
2409 vLockedArgs.push_back("-datadir=" + tmp);
2410 vLockedArgs.push_back("-walletbackend=sqlite");
2411 vLockedArgs.push_back("-recoveryaudit");
2412 vLockedArgs.push_back("-nogui");
2413 int nLockedRet = RunBitflashChild(strExe, vLockedArgs, NULL, &strLockedAudit);
2414 nFail += Check(nLockedRet == 2 &&
2415 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit"),
2416 "a locked encrypted SQLite wallet reports that phrase coverage needs unlock") ? 0 : 1;
2417
2418 // The wrong passphrase is rejected and reveals nothing.
2419 string strWrongDump = tmp + "/wrong-dump.txt";
2420 vector<string> vWrongArgs;
2421 vWrongArgs.push_back("-datadir=" + tmp);
2422 vWrongArgs.push_back("-walletbackend=sqlite");
2423 vWrongArgs.push_back("-walletpassphrase=@" + strWrongPassFile);
2424 vWrongArgs.push_back("-dumpwallet=" + strWrongDump);
2425 vWrongArgs.push_back("-nogui");
2426 int nWrongRet = RunBitflashChild(strExe, vWrongArgs);
2427 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
2428 "a restarted SQLite wallet rejects the wrong passphrase") ? 0 : 1;
2429
2430 // The right passphrase unlocks and can dump the original key.
2431 string strRightDump = tmp + "/right-dump.txt";
2432 vector<string> vRightArgs;
2433 vRightArgs.push_back("-datadir=" + tmp);
2434 vRightArgs.push_back("-walletbackend=sqlite");
2435 vRightArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2436 vRightArgs.push_back("-dumpwallet=" + strRightDump);
2437 vRightArgs.push_back("-nogui");
2438 int nRightRet = RunBitflashChild(strExe, vRightArgs);
2439 nFail += Check(nRightRet == 0 && FileExists(strRightDump.c_str()),
2440 "a restarted SQLite wallet unlocks with the right passphrase") ? 0 : 1;
2441 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
2442 "the unlocked SQLite wallet decrypts and dumps the original key") ? 0 : 1;
2443
2444 // The recovery phrase survives the encryption.
2445 string strAudit = tmp + "/recovery-audit.txt";
2446 vector<string> vAuditArgs;
2447 vAuditArgs.push_back("-datadir=" + tmp);
2448 vAuditArgs.push_back("-walletbackend=sqlite");
2449 vAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
2450 vAuditArgs.push_back("-recoveryaudit");
2451 vAuditArgs.push_back("-nogui");
2452 int nAuditRet = RunBitflashChild(strExe, vAuditArgs, NULL, &strAudit);
2453 nFail += Check(nAuditRet == 0 &&
2454 FileContainsText(strAudit, "recovery phrase: present"),
2455 "the encrypted SQLite wallet still has its recovery phrase") ? 0 : 1;
2456
2457 // One-shot storage commands honour -walletbackend. The SQLite wallet is
2458 // encrypted; the wallet.dat beside it is still plaintext. Same datadir,
2459 // opposite answers -> the flag routes the diagnostic to the right file.
2460 string strSqliteAuditJson = tmp + "/sqlite-audit.json";
2461 vector<string> vSqliteAuditArgs;
2462 vSqliteAuditArgs.push_back("-datadir=" + tmp);
2463 vSqliteAuditArgs.push_back("-walletbackend=sqlite");
2464 vSqliteAuditArgs.push_back("-walletstorageauditjson=" + strSqliteAuditJson);
2465 vSqliteAuditArgs.push_back("-nogui");
2466 int nSqliteAuditRet = RunBitflashChild(strExe, vSqliteAuditArgs);
2467 nFail += Check(nSqliteAuditRet == 0 &&
2468 FileContainsText(strSqliteAuditJson, "\"encrypted_hd_seed\": \"complete\"") &&
2469 FileContainsText(strSqliteAuditJson, "\"plain_hd_seed\": \"none\""),
2470 "-walletstorageaudit under SQLite reads the encrypted wallet.sqlite") ? 0 : 1;
2471
2472 string strBdbAuditJson = tmp + "/bdb-audit.json";
2473 vector<string> vBdbAuditArgs;
2474 vBdbAuditArgs.push_back("-datadir=" + tmp);
2475 vBdbAuditArgs.push_back("-walletstorageauditjson=" + strBdbAuditJson);
2476 vBdbAuditArgs.push_back("-nogui");
2477 int nBdbAuditRet = RunBitflashChild(strExe, vBdbAuditArgs);
2478 nFail += Check(nBdbAuditRet == 0 &&
2479 FileContainsText(strBdbAuditJson, "\"plain_hd_seed\": \"complete\"") &&
2480 FileContainsText(strBdbAuditJson, "\"encrypted_hd_seed\": \"none\""),
2481 "-walletstorageaudit without the flag still reads the plaintext wallet.dat") ? 0 : 1;
2482
2483 string strSqliteCheckOut = tmp + "/sqlite-check.txt";
2484 vector<string> vSqliteCheckArgs;
2485 vSqliteCheckArgs.push_back("-datadir=" + tmp);
2486 vSqliteCheckArgs.push_back("-walletbackend=sqlite");
2487 vSqliteCheckArgs.push_back("-walletstoragecheck");
2488 vSqliteCheckArgs.push_back("-nogui");
2489 int nSqliteCheckRet = RunBitflashChild(strExe, vSqliteCheckArgs, NULL, &strSqliteCheckOut);
2490 nFail += Check(nSqliteCheckRet == 0,
2491 "-walletstoragecheck under SQLite passes on the encrypted wallet.sqlite") ? 0 : 1;
2492
2493 // Backup under SQLite copies wallet.sqlite, not the plaintext wallet.dat.
2494 // The copy is a standalone SQLite wallet (loadcheck accepts it) and holds
2495 // none of the plaintext key that wallet.dat still contains.
2496 string strBackupDest = tmp + "/backup.sqlite";
2497 vector<string> vBackupArgs;
2498 vBackupArgs.push_back("-datadir=" + tmp);
2499 vBackupArgs.push_back("-walletbackend=sqlite");
2500 vBackupArgs.push_back("-backupwallet=" + strBackupDest);
2501 vBackupArgs.push_back("-nogui");
2502 int nBackupRet = RunBitflashChild(strExe, vBackupArgs);
2503 nFail += Check(nBackupRet == 0 && FileExists(strBackupDest.c_str()),
2504 "-backupwallet under SQLite writes a backup file") ? 0 : 1;
2505 nFail += Check(!FileContainsBytes(strBackupDest, vchDefaultPrivBytes),
2506 "the SQLite backup holds no plaintext key (it copied wallet.sqlite, not wallet.dat)") ? 0 : 1;
2507
2508 string strBackupLoadCheck = tmp + "/backup-loadcheck.txt";
2509 vector<string> vLoadCheckArgs;
2510 vLoadCheckArgs.push_back("-datadir=" + tmp);
2511 vLoadCheckArgs.push_back("-walletsqliteloadcheck=" + strBackupDest);
2512 vLoadCheckArgs.push_back("-nogui");
2513 int nBackupLoadRet = RunBitflashChild(strExe, vLoadCheckArgs, NULL, &strBackupLoadCheck);
2514 nFail += Check(nBackupLoadRet == 0,
2515 "the SQLite backup is a valid standalone SQLite wallet") ? 0 : 1;
2516 }
2517 catch (const std::exception& e)
2518 {
2519 printf(" FAIL exception: %s\n", e.what());
2520 nFail++;
2521 }
2522 catch (...)
2523 {
2524 printf(" FAIL unknown exception\n");
2525 nFail++;
2526 }
2527
2528 DBFlush(true);
2529 SetCurrentDir(cwd);
2530 RemoveTree(tmp);
2531 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
2532 nFail, nFail == 1 ? "" : "s");
2533 fflush(stdout);
2534 return nFail == 0 ? 0 : 1;
2535}
2536
2255static int RunWalletPortabilitySelfTest() 2537static int RunWalletPortabilitySelfTest()
2256{ 2538{
2257 fflush(stdout); 2539 fflush(stdout);
@@ -2981,6 +3263,8 @@ int RunSelfTest(const std::string& name)
2981 return RunWalletCryptoSelfTest(); 3263 return RunWalletCryptoSelfTest();
2982 if (name == "wallet-encrypt") 3264 if (name == "wallet-encrypt")
2983 return RunWalletEncryptSelfTest(); 3265 return RunWalletEncryptSelfTest();
3266 if (name == "wallet-sqlite-encrypt")
3267 return RunWalletSQLiteEncryptSelfTest();
2984 if (name == "wallet-portability") 3268 if (name == "wallet-portability")
2985 return RunWalletPortabilitySelfTest(); 3269 return RunWalletPortabilitySelfTest();
2986 if (name == "net-message") 3270 if (name == "net-message")
@@ -2997,6 +3281,6 @@ int RunSelfTest(const std::string& name)
2997 return RunManagedTorSelfTest(); 3281 return RunManagedTorSelfTest();
2998 3282
2999 printf("Unknown self-test '%s'\n", name.c_str()); 3283 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"); 3284 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; 3285 return 1;
3002} 3286}