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:
Diffstat (limited to 'src')
-rw-r--r--src/main.cpp3
-rw-r--r--src/main.h2
-rw-r--r--src/main_gui.cpp18
-rw-r--r--src/selftest.cpp336
-rw-r--r--src/walletcmd.cpp263
-rw-r--r--src/walletcmd.h4
6 files changed, 620 insertions, 6 deletions
diff --git a/src/main.cpp b/src/main.cpp
index f83164f..5bd4028 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4355,6 +4355,9 @@ WalletRecoveryAudit GetWalletRecoveryAudit()
4355 CRITICAL_BLOCK(cs_keyPool) 4355 CRITICAL_BLOCK(cs_keyPool)
4356 { 4356 {
4357 audit.fHaveSeed = HaveHDSeed(); 4357 audit.fHaveSeed = HaveHDSeed();
4358 audit.fSeedEncryptedLocked = !audit.fHaveSeed &&
4359 (!vchCryptedHDMaster.empty() ||
4360 !vchCryptedHDChainCode.empty());
4358 audit.nSchema = nHDKeySchema; 4361 audit.nSchema = nHDKeySchema;
4359 audit.nDerivedKnown = nHDNext; 4362 audit.nDerivedKnown = nHDNext;
4360 audit.nReceiveNext = nHDReceiveNext; 4363 audit.nReceiveNext = nHDReceiveNext;
diff --git a/src/main.h b/src/main.h
index b389f1d..dc5527e 100644
--- a/src/main.h
+++ b/src/main.h
@@ -292,6 +292,7 @@ int64 GetBalance();
292struct WalletRecoveryAudit 292struct WalletRecoveryAudit
293{ 293{
294 bool fHaveSeed; 294 bool fHaveSeed;
295 bool fSeedEncryptedLocked;
295 bool fDeriveComplete; 296 bool fDeriveComplete;
296 int nSchema; 297 int nSchema;
297 unsigned int nDerivedKnown; 298 unsigned int nDerivedKnown;
@@ -311,6 +312,7 @@ struct WalletRecoveryAudit
311 WalletRecoveryAudit() 312 WalletRecoveryAudit()
312 { 313 {
313 fHaveSeed = false; 314 fHaveSeed = false;
315 fSeedEncryptedLocked = false;
314 fDeriveComplete = true; 316 fDeriveComplete = true;
315 nSchema = HD_SCHEMA_NONE; 317 nSchema = HD_SCHEMA_NONE;
316 nDerivedKnown = 0; 318 nDerivedKnown = 0;
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 3f52e91..395cc0f 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -114,7 +114,8 @@ static void PrintUsage()
114 printf(" /gen\n"); 114 printf(" /gen\n");
115 printf(" /nogui or /daemon\n"); 115 printf(" /nogui or /daemon\n");
116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); 116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n");
117 printf(" net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, or managed-tor\n"); 117 printf(" wallet-portability, net-message, consensus-limits, pool-stratum,\n");
118 printf(" parse-money, socks5-proxy, or managed-tor\n");
118 printf("\n"); 119 printf("\n");
119 printf("Mining mode:\n"); 120 printf("Mining mode:\n");
120 printf(" /operator\n"); 121 printf(" /operator\n");
@@ -171,6 +172,10 @@ static void PrintUsage()
171 printf(" /recoveryaudit (show how much spendable balance is\n"); 172 printf(" /recoveryaudit (show how much spendable balance is\n");
172 printf(" covered by the recovery phrase;\n"); 173 printf(" covered by the recovery phrase;\n");
173 printf(" exits 2 when wallet.dat is still needed)\n"); 174 printf(" exits 2 when wallet.dat is still needed)\n");
175 printf(" /walletstorageaudit (count wallet.dat record types without\n");
176 printf(" printing wallet values, then exit)\n");
177 printf(" /walletstorageauditjson=FILE\n");
178 printf(" (write the same audit as deterministic JSON)\n");
174 printf(" /rescan (walk the chain for coins this wallet owns\n"); 179 printf(" /rescan (walk the chain for coins this wallet owns\n");
175 printf(" but never recorded, then exit)\n"); 180 printf(" but never recorded, then exit)\n");
176 printf("\n"); 181 printf("\n");
@@ -703,6 +708,17 @@ int main(int argc, char* argv[])
703 return nRet; 708 return nRet;
704 } 709 }
705 710
711 string strWalletStorageAuditJson =
712 argval2(argc, argv, "/walletstorageauditjson", "-walletstorageauditjson");
713 if (arg(argc,argv,"/walletstorageaudit") ||
714 arg(argc,argv,"-walletstorageaudit") ||
715 !strWalletStorageAuditJson.empty())
716 {
717 int nRet = CmdWalletStorageAudit(strWalletStorageAuditJson);
718 DBFlush(true);
719 return nRet;
720 }
721
706 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase")) 722 if (arg(argc,argv,"/newphrase") || arg(argc,argv,"-newphrase"))
707 { 723 {
708 int nRet = CmdNewPhrase(); 724 int nRet = CmdNewPhrase();
diff --git a/src/selftest.cpp b/src/selftest.cpp
index 3c93927..73c2935 100644
--- a/src/selftest.cpp
+++ b/src/selftest.cpp
@@ -34,6 +34,7 @@ extern int RunPoolStratumSelfTest();
34#undef printf 34#undef printf
35 35
36#include <mutex> 36#include <mutex>
37#include <cerrno>
37#include <stdexcept> 38#include <stdexcept>
38#include <thread> 39#include <thread>
39 40
@@ -925,6 +926,90 @@ static bool WriteTextFile(const string& strPath, const string& strText)
925 return fOk; 926 return fOk;
926} 927}
927 928
929static bool MakeDirLocal(const string& strPath)
930{
931#ifdef _WIN32
932 return CreateDirectoryA(strPath.c_str(), NULL) || GetLastError() == ERROR_ALREADY_EXISTS;
933#else
934 return mkdir(strPath.c_str(), 0700) == 0 || errno == EEXIST;
935#endif
936}
937
938static bool CopyFileLocal(const string& strSrc, const string& strDst)
939{
940 FILE* in = fopen(strSrc.c_str(), "rb");
941 if (!in)
942 return false;
943 FILE* out = fopen(strDst.c_str(), "wb");
944 if (!out)
945 {
946 fclose(in);
947 return false;
948 }
949
950 bool fOk = true;
951 char buf[65536];
952 size_t n = 0;
953 while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
954 {
955 if (fwrite(buf, 1, n, out) != n)
956 {
957 fOk = false;
958 break;
959 }
960 }
961 if (ferror(in))
962 fOk = false;
963 if (fclose(out) != 0)
964 fOk = false;
965 fclose(in);
966 return fOk;
967}
968
969static bool DirectoryHasFileWithPrefix(const string& strDir, const string& strPrefix)
970{
971#ifdef _WIN32
972 WIN32_FIND_DATAA findData;
973 string pattern = strDir + "\\*";
974 HANDLE hFind = FindFirstFileA(pattern.c_str(), &findData);
975 if (hFind == INVALID_HANDLE_VALUE)
976 return false;
977 bool fFound = false;
978 do
979 {
980 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
981 {
982 string name = findData.cFileName;
983 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
984 {
985 fFound = true;
986 break;
987 }
988 }
989 }
990 while (FindNextFileA(hFind, &findData));
991 FindClose(hFind);
992 return fFound;
993#else
994 DIR* dir = opendir(strDir.c_str());
995 if (!dir)
996 return false;
997 bool fFound = false;
998 struct dirent* ent;
999 while ((ent = readdir(dir)) != NULL)
1000 {
1001 string name = ent->d_name;
1002 if (name.compare(0, strPrefix.size(), strPrefix) == 0)
1003 {
1004 fFound = true;
1005 break;
1006 }
1007 }
1008 closedir(dir);
1009 return fFound;
1010#endif
1011}
1012
928static string QuoteCommandArg(const string& str) 1013static string QuoteCommandArg(const string& str)
929{ 1014{
930 string out = "\""; 1015 string out = "\"";
@@ -941,7 +1026,8 @@ static string QuoteCommandArg(const string& str)
941 1026
942static int RunBitflashChild(const string& strExe, 1027static int RunBitflashChild(const string& strExe,
943 const vector<string>& vArgs, 1028 const vector<string>& vArgs,
944 const string* pStdinFile = NULL) 1029 const string* pStdinFile = NULL,
1030 const string* pOutputFile = NULL)
945{ 1031{
946#ifdef _WIN32 1032#ifdef _WIN32
947 vector<char*> argv; 1033 vector<char*> argv;
@@ -954,8 +1040,16 @@ static int RunBitflashChild(const string& strExe,
954 int nOldErr = _dup(2); 1040 int nOldErr = _dup(2);
955 int nOldIn = _dup(0); 1041 int nOldIn = _dup(0);
956 int nNull = _open("NUL", _O_WRONLY); 1042 int nNull = _open("NUL", _O_WRONLY);
1043 int nOut = -1;
957 int nIn = -1; 1044 int nIn = -1;
958 if (nNull >= 0) 1045 if (pOutputFile)
1046 nOut = _open(pOutputFile->c_str(), _O_WRONLY|_O_CREAT|_O_TRUNC|_O_BINARY, _S_IREAD|_S_IWRITE);
1047 if (nOut >= 0)
1048 {
1049 _dup2(nOut, 1);
1050 _dup2(nOut, 2);
1051 }
1052 else if (nNull >= 0)
959 { 1053 {
960 _dup2(nNull, 1); 1054 _dup2(nNull, 1);
961 _dup2(nNull, 2); 1055 _dup2(nNull, 2);
@@ -969,6 +1063,8 @@ static int RunBitflashChild(const string& strExe,
969 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]); 1063 int nRet = _spawnv(_P_WAIT, strExe.c_str(), &argv[0]);
970 if (nIn >= 0) 1064 if (nIn >= 0)
971 _close(nIn); 1065 _close(nIn);
1066 if (nOut >= 0)
1067 _close(nOut);
972 if (nNull >= 0) 1068 if (nNull >= 0)
973 _close(nNull); 1069 _close(nNull);
974 if (nOldIn >= 0) 1070 if (nOldIn >= 0)
@@ -993,7 +1089,10 @@ static int RunBitflashChild(const string& strExe,
993 strCmd += " " + QuoteCommandArg(vArgs[i]); 1089 strCmd += " " + QuoteCommandArg(vArgs[i]);
994 if (pStdinFile) 1090 if (pStdinFile)
995 strCmd += " < " + QuoteCommandArg(*pStdinFile); 1091 strCmd += " < " + QuoteCommandArg(*pStdinFile);
996 strCmd += " > /dev/null 2>&1"; 1092 if (pOutputFile)
1093 strCmd += " > " + QuoteCommandArg(*pOutputFile) + " 2>&1";
1094 else
1095 strCmd += " > /dev/null 2>&1";
997 return system(strCmd.c_str()); 1096 return system(strCmd.c_str());
998#endif 1097#endif
999} 1098}
@@ -1066,6 +1165,8 @@ static int RunWalletEncryptSelfTest()
1066 string strRightDump = tmp + "/right-pass-dump.txt"; 1165 string strRightDump = tmp + "/right-pass-dump.txt";
1067 string strLiteralDump = tmp + "/literal-pass-dump.txt"; 1166 string strLiteralDump = tmp + "/literal-pass-dump.txt";
1068 string strStdinDump = tmp + "/stdin-pass-dump.txt"; 1167 string strStdinDump = tmp + "/stdin-pass-dump.txt";
1168 string strLockedAudit = tmp + "/locked-recovery-audit.txt";
1169 string strUnlockedAudit = tmp + "/unlocked-recovery-audit.txt";
1069 string strWrongPassFile = tmp + "/wrong-pass.txt"; 1170 string strWrongPassFile = tmp + "/wrong-pass.txt";
1070 string strRightPassFile = tmp + "/right-pass.txt"; 1171 string strRightPassFile = tmp + "/right-pass.txt";
1071 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") && 1172 nFail += Check(WriteTextFile(strWrongPassFile, "wrong-passphrase\n") &&
@@ -1081,6 +1182,17 @@ static int RunWalletEncryptSelfTest()
1081 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()), 1182 nFail += Check(nWrongRet != 0 && !FileExists(strWrongDump.c_str()),
1082 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1; 1183 "a restarted wallet rejects the wrong-passphrase") ? 0 : 1;
1083 1184
1185 vector<string> vLockedAuditArgs;
1186 vLockedAuditArgs.push_back("-datadir=" + tmp);
1187 vLockedAuditArgs.push_back("-recoveryaudit");
1188 vLockedAuditArgs.push_back("-nogui");
1189 int nLockedAuditRet = RunBitflashChild(strExe, vLockedAuditArgs, NULL, &strLockedAudit);
1190 nFail += Check(nLockedAuditRet == 2 &&
1191 FileContainsText(strLockedAudit, "recovery phrase: encrypted, unlock wallet to audit") &&
1192 FileContainsText(strLockedAudit, "recovery coverage: unavailable while wallet is locked") &&
1193 FileContainsText(strLockedAudit, "Unlock the wallet with /walletpassphrase"),
1194 "a locked encrypted wallet reports that phrase coverage needs unlock") ? 0 : 1;
1195
1084 vector<string> vLiteralArgs; 1196 vector<string> vLiteralArgs;
1085 vLiteralArgs.push_back("-datadir=" + tmp); 1197 vLiteralArgs.push_back("-datadir=" + tmp);
1086 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase"); 1198 vLiteralArgs.push_back("-walletpassphrase=btf-test-passphrase");
@@ -1101,6 +1213,17 @@ static int RunWalletEncryptSelfTest()
1101 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)), 1213 nFail += Check(FileContainsText(strRightDump, HexStrLocal(vchDefaultPrivBytes)),
1102 "the restarted wallet can decrypt and dump the original key") ? 0 : 1; 1214 "the restarted wallet can decrypt and dump the original key") ? 0 : 1;
1103 1215
1216 vector<string> vUnlockedAuditArgs;
1217 vUnlockedAuditArgs.push_back("-datadir=" + tmp);
1218 vUnlockedAuditArgs.push_back("-walletpassphrase=@" + strRightPassFile);
1219 vUnlockedAuditArgs.push_back("-recoveryaudit");
1220 vUnlockedAuditArgs.push_back("-nogui");
1221 int nUnlockedAuditRet = RunBitflashChild(strExe, vUnlockedAuditArgs, NULL, &strUnlockedAudit);
1222 nFail += Check(nUnlockedAuditRet == 0 &&
1223 FileContainsText(strUnlockedAudit, "recovery phrase: present") &&
1224 !FileContainsText(strUnlockedAudit, "recovery phrase: not installed"),
1225 "an unlocked encrypted wallet audits the installed recovery phrase") ? 0 : 1;
1226
1104 vector<string> vStdinArgs; 1227 vector<string> vStdinArgs;
1105 vStdinArgs.push_back("-datadir=" + tmp); 1228 vStdinArgs.push_back("-datadir=" + tmp);
1106 vStdinArgs.push_back("-walletpassphrase"); 1229 vStdinArgs.push_back("-walletpassphrase");
@@ -1131,6 +1254,209 @@ static int RunWalletEncryptSelfTest()
1131 return nFail == 0 ? 0 : 1; 1254 return nFail == 0 ? 0 : 1;
1132} 1255}
1133 1256
1257static int RunWalletPortabilitySelfTest()
1258{
1259 fflush(stdout);
1260 printf("wallet-portability self-test\n");
1261
1262 std::string tmp;
1263 std::string cwd;
1264 if (!MakeTempDir(tmp))
1265 {
1266 printf(" FAIL could not create a temporary data directory\n");
1267 return 1;
1268 }
1269 if (!GetCurrentDir(cwd))
1270 {
1271 printf(" FAIL could not read current directory\n");
1272 RemoveTree(tmp);
1273 return 1;
1274 }
1275
1276 int nFail = 0;
1277 printf(" temp root: %s\n", tmp.c_str());
1278
1279 try
1280 {
1281#ifdef _WIN32
1282 string strExe = cwd + "\\bitflash.exe";
1283#else
1284 string strExe = cwd + "/bitflash-node";
1285#endif
1286 string strOriginal = tmp + "/original";
1287 string strRawCopy = tmp + "/raw-copy";
1288 string strBackupCopy = tmp + "/backup-copy";
1289 nFail += Check(MakeDirLocal(strOriginal) &&
1290 MakeDirLocal(strRawCopy) &&
1291 MakeDirLocal(strBackupCopy),
1292 "temporary wallet directories can be created") ? 0 : 1;
1293
1294 string strCreateOut = tmp + "/create.txt";
1295 vector<string> vCreateArgs;
1296 vCreateArgs.push_back("-datadir=" + strOriginal);
1297 vCreateArgs.push_back("-nomanagedtor");
1298 vCreateArgs.push_back("-nogui");
1299 vCreateArgs.push_back("-newphrase");
1300 int nCreateRet = RunBitflashChild(strExe, vCreateArgs, NULL, &strCreateOut);
1301 nFail += Check(nCreateRet == 0 &&
1302 FileContainsText(strCreateOut, "Write these twelve words down"),
1303 "a phrase wallet can be created in a fresh datadir") ? 0 : 1;
1304
1305 string strOriginalAudit = tmp + "/original-audit.txt";
1306 vector<string> vOriginalAuditArgs;
1307 vOriginalAuditArgs.push_back("-datadir=" + strOriginal);
1308 vOriginalAuditArgs.push_back("-nomanagedtor");
1309 vOriginalAuditArgs.push_back("-nogui");
1310 vOriginalAuditArgs.push_back("-recoveryaudit");
1311 int nOriginalAuditRet = RunBitflashChild(strExe, vOriginalAuditArgs, NULL, &strOriginalAudit);
1312 nFail += Check(nOriginalAuditRet == 0 &&
1313 FileContainsText(strOriginalAudit, "recovery phrase: present") &&
1314 FileContainsText(strOriginalAudit, "SLIP-0044 BITFLASH"),
1315 "the original wallet reports its recovery phrase") ? 0 : 1;
1316
1317 string strOriginalStorageAudit = tmp + "/original-storage-audit.txt";
1318 vector<string> vOriginalStorageAuditArgs;
1319 vOriginalStorageAuditArgs.push_back("-datadir=" + strOriginal);
1320 vOriginalStorageAuditArgs.push_back("-nomanagedtor");
1321 vOriginalStorageAuditArgs.push_back("-nogui");
1322 vOriginalStorageAuditArgs.push_back("-walletstorageaudit");
1323 int nOriginalStorageAuditRet =
1324 RunBitflashChild(strExe, vOriginalStorageAuditArgs, NULL, &strOriginalStorageAudit);
1325 nFail += Check(nOriginalStorageAuditRet == 0 &&
1326 FileContainsText(strOriginalStorageAudit, "Wallet storage audit") &&
1327 FileContainsText(strOriginalStorageAudit, "plain HD seed: complete") &&
1328 FileContainsText(strOriginalStorageAudit, "encrypted HD seed: none") &&
1329 FileContainsText(strOriginalStorageAudit, "malformed records: 0"),
1330 "the storage audit recognizes a plaintext phrase wallet") ? 0 : 1;
1331
1332 string strOriginalStorageAuditJson = tmp + "/original-storage-audit.json";
1333 vector<string> vOriginalStorageAuditJsonArgs;
1334 vOriginalStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
1335 vOriginalStorageAuditJsonArgs.push_back("-nomanagedtor");
1336 vOriginalStorageAuditJsonArgs.push_back("-nogui");
1337 vOriginalStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
1338 strOriginalStorageAuditJson);
1339 int nOriginalStorageAuditJsonRet =
1340 RunBitflashChild(strExe, vOriginalStorageAuditJsonArgs);
1341 nFail += Check(nOriginalStorageAuditJsonRet == 0 &&
1342 FileContainsText(strOriginalStorageAuditJson,
1343 "\"format\": \"bitflash-wallet-storage-audit-v1\"") &&
1344 FileContainsText(strOriginalStorageAuditJson,
1345 "\"plain_hd_seed\": \"complete\"") &&
1346 FileContainsText(strOriginalStorageAuditJson,
1347 "\"encrypted_hd_seed\": \"none\""),
1348 "the storage audit can write plaintext-wallet JSON") ? 0 : 1;
1349
1350 string strPortableWallet = tmp + "/portable-wallet.dat";
1351 string strBackupOut = tmp + "/backupwallet.txt";
1352 vector<string> vBackupArgs;
1353 vBackupArgs.push_back("-datadir=" + strOriginal);
1354 vBackupArgs.push_back("-nomanagedtor");
1355 vBackupArgs.push_back("-nogui");
1356 vBackupArgs.push_back("-backupwallet=" + strPortableWallet);
1357 int nBackupRet = RunBitflashChild(strExe, vBackupArgs, NULL, &strBackupOut);
1358 nFail += Check(nBackupRet == 0 && FileExists(strPortableWallet.c_str()),
1359 "/backupwallet writes a portable wallet.dat") ? 0 : 1;
1360
1361 nFail += Check(CopyFileLocal(strOriginal + "/wallet.dat",
1362 strRawCopy + "/wallet.dat"),
1363 "a raw wallet.dat can be copied without database logs") ? 0 : 1;
1364 string strRawAudit = tmp + "/raw-copy-audit.txt";
1365 vector<string> vRawAuditArgs;
1366 vRawAuditArgs.push_back("-datadir=" + strRawCopy);
1367 vRawAuditArgs.push_back("-nomanagedtor");
1368 vRawAuditArgs.push_back("-nogui");
1369 vRawAuditArgs.push_back("-recoveryaudit");
1370 int nRawAuditRet = RunBitflashChild(strExe, vRawAuditArgs, NULL, &strRawAudit);
1371 nFail += Check(nRawAuditRet == 0 &&
1372 FileContainsText(strRawAudit, "recovery phrase: present") &&
1373 !FileContainsText(strRawAudit, "Cannot open wallet.dat"),
1374 "a raw wallet.dat copy opens in a new datadir") ? 0 : 1;
1375
1376 nFail += Check(CopyFileLocal(strPortableWallet,
1377 strBackupCopy + "/wallet.dat"),
1378 "the portable backup can be installed as wallet.dat") ? 0 : 1;
1379 string strBackupAudit = tmp + "/backup-copy-audit.txt";
1380 vector<string> vBackupAuditArgs;
1381 vBackupAuditArgs.push_back("-datadir=" + strBackupCopy);
1382 vBackupAuditArgs.push_back("-nomanagedtor");
1383 vBackupAuditArgs.push_back("-nogui");
1384 vBackupAuditArgs.push_back("-recoveryaudit");
1385 int nBackupAuditRet = RunBitflashChild(strExe, vBackupAuditArgs, NULL, &strBackupAudit);
1386 nFail += Check(nBackupAuditRet == 0 &&
1387 FileContainsText(strBackupAudit, "recovery phrase: present") &&
1388 FileContainsText(strBackupAudit, "SLIP-0044 BITFLASH"),
1389 "a /backupwallet copy opens in a new datadir") ? 0 : 1;
1390
1391 string strPassFile = tmp + "/encrypt-pass.txt";
1392 string strEncryptOut = tmp + "/encryptwallet.txt";
1393 nFail += Check(WriteTextFile(strPassFile, "portable-test-passphrase\n"),
1394 "an encryption passphrase file can be written") ? 0 : 1;
1395 vector<string> vEncryptArgs;
1396 vEncryptArgs.push_back("-datadir=" + strOriginal);
1397 vEncryptArgs.push_back("-nomanagedtor");
1398 vEncryptArgs.push_back("-nogui");
1399 vEncryptArgs.push_back("-encryptwallet=@" + strPassFile);
1400 int nEncryptRet = RunBitflashChild(strExe, vEncryptArgs, NULL, &strEncryptOut);
1401 nFail += Check(nEncryptRet == 0 &&
1402 !DirectoryHasFileWithPrefix(strOriginal + "/database", "log."),
1403 "the encryptwallet command purges Berkeley DB environment logs") ? 0 : 1;
1404
1405 string strEncryptedStorageAudit = tmp + "/encrypted-storage-audit.txt";
1406 vector<string> vEncryptedStorageAuditArgs;
1407 vEncryptedStorageAuditArgs.push_back("-datadir=" + strOriginal);
1408 vEncryptedStorageAuditArgs.push_back("-nomanagedtor");
1409 vEncryptedStorageAuditArgs.push_back("-nogui");
1410 vEncryptedStorageAuditArgs.push_back("-walletstorageaudit");
1411 int nEncryptedStorageAuditRet =
1412 RunBitflashChild(strExe, vEncryptedStorageAuditArgs, NULL, &strEncryptedStorageAudit);
1413 nFail += Check(nEncryptedStorageAuditRet == 0 &&
1414 FileContainsText(strEncryptedStorageAudit, "plain private keys: 0") &&
1415 FileContainsText(strEncryptedStorageAudit, "encrypted private keys:") &&
1416 FileContainsText(strEncryptedStorageAudit, "plain HD seed: none") &&
1417 FileContainsText(strEncryptedStorageAudit, "encrypted HD seed: complete") &&
1418 FileContainsText(strEncryptedStorageAudit, "wallet minimum version: present") &&
1419 FileContainsText(strEncryptedStorageAudit, "malformed records: 0"),
1420 "the storage audit recognizes an encrypted wallet") ? 0 : 1;
1421
1422 string strEncryptedStorageAuditJson = tmp + "/encrypted-storage-audit.json";
1423 vector<string> vEncryptedStorageAuditJsonArgs;
1424 vEncryptedStorageAuditJsonArgs.push_back("-datadir=" + strOriginal);
1425 vEncryptedStorageAuditJsonArgs.push_back("-nomanagedtor");
1426 vEncryptedStorageAuditJsonArgs.push_back("-nogui");
1427 vEncryptedStorageAuditJsonArgs.push_back("-walletstorageauditjson=" +
1428 strEncryptedStorageAuditJson);
1429 int nEncryptedStorageAuditJsonRet =
1430 RunBitflashChild(strExe, vEncryptedStorageAuditJsonArgs);
1431 nFail += Check(nEncryptedStorageAuditJsonRet == 0 &&
1432 FileContainsText(strEncryptedStorageAuditJson,
1433 "\"plain_private_keys\": 0") &&
1434 FileContainsText(strEncryptedStorageAuditJson,
1435 "\"plain_hd_seed\": \"none\"") &&
1436 FileContainsText(strEncryptedStorageAuditJson,
1437 "\"encrypted_hd_seed\": \"complete\"") &&
1438 FileContainsText(strEncryptedStorageAuditJson,
1439 "\"wallet_minimum_version\": \"present\""),
1440 "the storage audit can write encrypted-wallet JSON") ? 0 : 1;
1441 }
1442 catch (const std::exception& e)
1443 {
1444 printf(" FAIL exception: %s\n", e.what());
1445 nFail++;
1446 }
1447 catch (...)
1448 {
1449 printf(" FAIL unknown exception\n");
1450 nFail++;
1451 }
1452
1453 RemoveTree(tmp);
1454 printf("%s (%d failure%s)\n", nFail == 0 ? "ALL TESTS PASSED" : "TESTS FAILED",
1455 nFail, nFail == 1 ? "" : "s");
1456 fflush(stdout);
1457 return nFail == 0 ? 0 : 1;
1458}
1459
1134static int RunNetMessageSelfTest() 1460static int RunNetMessageSelfTest()
1135{ 1461{
1136 fflush(stdout); 1462 fflush(stdout);
@@ -1615,6 +1941,8 @@ int RunSelfTest(const std::string& name)
1615 return RunWalletCryptoSelfTest(); 1941 return RunWalletCryptoSelfTest();
1616 if (name == "wallet-encrypt") 1942 if (name == "wallet-encrypt")
1617 return RunWalletEncryptSelfTest(); 1943 return RunWalletEncryptSelfTest();
1944 if (name == "wallet-portability")
1945 return RunWalletPortabilitySelfTest();
1618 if (name == "net-message") 1946 if (name == "net-message")
1619 return RunNetMessageSelfTest(); 1947 return RunNetMessageSelfTest();
1620 if (name == "consensus-limits") 1948 if (name == "consensus-limits")
@@ -1629,6 +1957,6 @@ int RunSelfTest(const std::string& name)
1629 return RunManagedTorSelfTest(); 1957 return RunManagedTorSelfTest();
1630 1958
1631 printf("Unknown self-test '%s'\n", name.c_str()); 1959 printf("Unknown self-test '%s'\n", name.c_str());
1632 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n"); 1960 printf("Known self-tests: wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt, wallet-portability, net-message, consensus-limits, pool-stratum, parse-money, socks5-proxy, managed-tor\n");
1633 return 1; 1961 return 1;
1634} 1962}
diff --git a/src/walletcmd.cpp b/src/walletcmd.cpp
index e8ac4bf..6f8258d 100644
--- a/src/walletcmd.cpp
+++ b/src/walletcmd.cpp
@@ -400,7 +400,12 @@ int CmdRecoveryAudit()
400 int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit; 400 int64 nImmatureTotal = audit.nRecoverableImmatureCredit + audit.nLegacyImmatureCredit;
401 401
402 printf("Wallet recovery audit\n"); 402 printf("Wallet recovery audit\n");
403 printf(" recovery phrase: %s\n", audit.fHaveSeed ? "present" : "not installed"); 403 if (audit.fHaveSeed)
404 printf(" recovery phrase: present\n");
405 else if (audit.fSeedEncryptedLocked)
406 printf(" recovery phrase: encrypted, unlock wallet to audit\n");
407 else
408 printf(" recovery phrase: not installed\n");
404 if (audit.fHaveSeed) 409 if (audit.fHaveSeed)
405 { 410 {
406 printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str()); 411 printf(" derivation schema: %s\n", HDKeySchemaName(audit.nSchema).c_str());
@@ -412,6 +417,15 @@ int CmdRecoveryAudit()
412 if (!audit.fDeriveComplete) 417 if (!audit.fDeriveComplete)
413 printf(" derivation warning: %s\n", audit.strDeriveError.c_str()); 418 printf(" derivation warning: %s\n", audit.strDeriveError.c_str());
414 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str()); 419 printf(" total spendable balance: %s BTF\n", FormatMoney(nTotal).c_str());
420 if (audit.fSeedEncryptedLocked)
421 {
422 printf(" recovery coverage: unavailable while wallet is locked\n");
423 printf(" immature mining rewards: %s BTF\n", FormatMoney(nImmatureTotal).c_str());
424 printf("\n");
425 printf("Unlock the wallet with /walletpassphrase or /walletpassphrase=@FILE to audit phrase coverage.\n");
426 fflush(stdout);
427 return 2;
428 }
415 printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n", 429 printf(" covered by recovery phrase: %s BTF (%d transaction(s))\n",
416 FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx); 430 FormatMoney(audit.nRecoverableCredit).c_str(), audit.nRecoverableTx);
417 printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n", 431 printf(" wallet.dat-only balance: %s BTF (%d transaction(s))\n",
@@ -456,6 +470,253 @@ int CmdRecoveryAudit()
456 return 0; 470 return 0;
457} 471}
458 472
473struct WalletStorageAuditCounts
474{
475 unsigned int nTotal;
476 unsigned int nMalformed;
477 unsigned int nUnknown;
478 unsigned int nVersion;
479 unsigned int nNames;
480 unsigned int nTransactions;
481 unsigned int nPlainKeys;
482 unsigned int nEncryptedKeys;
483 unsigned int nMasterKeys;
484 unsigned int nDefaultKey;
485 unsigned int nPlainHDMaster;
486 unsigned int nPlainHDChainCode;
487 unsigned int nCryptedHDMaster;
488 unsigned int nCryptedHDChainCode;
489 unsigned int nWalletMinVersion;
490 unsigned int nPool;
491 unsigned int nSettings;
492 unsigned int nHDNext;
493 unsigned int nHDSchema;
494 unsigned int nHDCoinType;
495 unsigned int nHDReceiveNext;
496 unsigned int nHDChangeNext;
497
498 WalletStorageAuditCounts()
499 {
500 memset(this, 0, sizeof(*this));
501 }
502};
503
504static std::string HDSeedStorageState(unsigned int nMaster, unsigned int nChain)
505{
506 if (nMaster == 0 && nChain == 0)
507 return "none";
508 if (nMaster > 0 && nChain > 0)
509 return "complete";
510 return "incomplete";
511}
512
513static void CountWalletStorageType(const std::string& strType,
514 WalletStorageAuditCounts& c)
515{
516 if (strType == "version")
517 c.nVersion++;
518 else if (strType == "name")
519 c.nNames++;
520 else if (strType == "tx")
521 c.nTransactions++;
522 else if (strType == "key")
523 c.nPlainKeys++;
524 else if (strType == "ckey")
525 c.nEncryptedKeys++;
526 else if (strType == "mkey")
527 c.nMasterKeys++;
528 else if (strType == "defaultkey")
529 c.nDefaultKey++;
530 else if (strType == "hdmaster")
531 c.nPlainHDMaster++;
532 else if (strType == "hdchaincode")
533 c.nPlainHDChainCode++;
534 else if (strType == "cryptedhdmaster")
535 c.nCryptedHDMaster++;
536 else if (strType == "cryptedhdchaincode")
537 c.nCryptedHDChainCode++;
538 else if (strType == "walletminversion")
539 c.nWalletMinVersion++;
540 else if (strType == "pool")
541 c.nPool++;
542 else if (strType == "setting")
543 c.nSettings++;
544 else if (strType == "hdnext")
545 c.nHDNext++;
546 else if (strType == "hdschema")
547 c.nHDSchema++;
548 else if (strType == "hdcointype")
549 c.nHDCoinType++;
550 else if (strType == "hdreceivenext")
551 c.nHDReceiveNext++;
552 else if (strType == "hdchangenext")
553 c.nHDChangeNext++;
554 else
555 c.nUnknown++;
556}
557
558static bool ReadWalletStorageCounts(WalletStorageAuditCounts& counts,
559 std::string& strError)
560{
561 class CWalletAuditDB : public CWalletDB
562 {
563 public:
564 CWalletAuditDB() : CWalletDB("r") { }
565 using CDB::GetCursor;
566 using CDB::ReadAtCursor;
567 };
568
569 CWalletAuditDB walletdb;
570 Dbc* pcursor = walletdb.GetCursor();
571 if (!pcursor)
572 {
573 strError = "cannot open wallet.dat cursor";
574 return false;
575 }
576
577 for (;;)
578 {
579 CDataStream ssKey(SER_DISK);
580 CDataStream ssValue(SER_DISK);
581 int ret = walletdb.ReadAtCursor(pcursor, ssKey, ssValue);
582 if (ret == DB_NOTFOUND)
583 break;
584 if (ret != 0)
585 {
586 pcursor->close();
587 strError = strprintf("Berkeley DB cursor read failed (%d)", ret);
588 return false;
589 }
590
591 counts.nTotal++;
592 try
593 {
594 std::string strType;
595 ssKey >> strType;
596 CountWalletStorageType(strType, counts);
597 }
598 catch (...)
599 {
600 counts.nMalformed++;
601 }
602 }
603
604 pcursor->close();
605 return true;
606}
607
608static std::string WalletStorageAuditJson(const WalletStorageAuditCounts& counts)
609{
610 std::string strJson;
611 strJson += "{\n";
612 strJson += " \"format\": \"bitflash-wallet-storage-audit-v1\",\n";
613 strJson += strprintf(" \"records_total\": %u,\n", counts.nTotal);
614 strJson += strprintf(" \"malformed_records\": %u,\n", counts.nMalformed);
615 strJson += strprintf(" \"unknown_records\": %u,\n", counts.nUnknown);
616 strJson += strprintf(" \"plain_private_keys\": %u,\n", counts.nPlainKeys);
617 strJson += strprintf(" \"encrypted_private_keys\": %u,\n", counts.nEncryptedKeys);
618 strJson += strprintf(" \"encryption_master_keys\": %u,\n", counts.nMasterKeys);
619 strJson += strprintf(" \"default_public_key\": \"%s\",\n",
620 counts.nDefaultKey ? "present" : "none");
621 strJson += strprintf(" \"plain_hd_seed\": \"%s\",\n",
622 HDSeedStorageState(counts.nPlainHDMaster,
623 counts.nPlainHDChainCode).c_str());
624 strJson += strprintf(" \"encrypted_hd_seed\": \"%s\",\n",
625 HDSeedStorageState(counts.nCryptedHDMaster,
626 counts.nCryptedHDChainCode).c_str());
627 strJson += strprintf(" \"wallet_minimum_version\": \"%s\",\n",
628 counts.nWalletMinVersion ? "present" : "none");
629 strJson += strprintf(" \"keypool_entries\": %u,\n", counts.nPool);
630 strJson += strprintf(" \"wallet_transactions\": %u,\n", counts.nTransactions);
631 strJson += strprintf(" \"address_book_labels\": %u,\n", counts.nNames);
632 strJson += strprintf(" \"settings\": %u,\n", counts.nSettings);
633 strJson += strprintf(" \"hd_metadata_records\": %u,\n",
634 counts.nHDNext + counts.nHDSchema + counts.nHDCoinType +
635 counts.nHDReceiveNext + counts.nHDChangeNext);
636 strJson += strprintf(" \"database_version_records\": %u\n", counts.nVersion);
637 strJson += "}\n";
638 return strJson;
639}
640
641static bool WriteAuditTextFile(const std::string& strPath,
642 const std::string& strText,
643 std::string& strError)
644{
645 FILE* pf = fopen(strPath.c_str(), "wb");
646 if (!pf)
647 {
648 strError = "cannot open output file";
649 return false;
650 }
651 size_t nWritten = fwrite(strText.data(), 1, strText.size(), pf);
652 bool fCloseOk = fclose(pf) == 0;
653 if (nWritten != strText.size() || !fCloseOk)
654 {
655 strError = "could not write the complete output file";
656 return false;
657 }
658 return true;
659}
660
661int CmdWalletStorageAudit(const std::string& strJsonOut)
662{
663 AttachTerminal();
664
665 WalletStorageAuditCounts counts;
666 std::string strError;
667 if (!ReadWalletStorageCounts(counts, strError))
668 {
669 fprintf(stderr, "Cannot audit wallet storage: %s\n", strError.c_str());
670 return 1;
671 }
672
673 if (!strJsonOut.empty())
674 {
675 std::string strWriteError;
676 if (!WriteAuditTextFile(strJsonOut,
677 WalletStorageAuditJson(counts),
678 strWriteError))
679 {
680 fprintf(stderr, "Cannot write wallet storage audit JSON: %s\n",
681 strWriteError.c_str());
682 return 1;
683 }
684 printf("Wallet storage audit written to %s\n", strJsonOut.c_str());
685 fflush(stdout);
686 return counts.nMalformed > 0 ? 2 : 0;
687 }
688
689 printf("Wallet storage audit\n");
690 printf(" records total: %u\n", counts.nTotal);
691 printf(" malformed records: %u\n", counts.nMalformed);
692 printf(" unknown records: %u\n", counts.nUnknown);
693 printf(" plain private keys: %u\n", counts.nPlainKeys);
694 printf(" encrypted private keys: %u\n", counts.nEncryptedKeys);
695 printf(" encryption master keys: %u\n", counts.nMasterKeys);
696 printf(" default public key: %s\n", counts.nDefaultKey ? "present" : "none");
697 printf(" plain HD seed: %s\n",
698 HDSeedStorageState(counts.nPlainHDMaster,
699 counts.nPlainHDChainCode).c_str());
700 printf(" encrypted HD seed: %s\n",
701 HDSeedStorageState(counts.nCryptedHDMaster,
702 counts.nCryptedHDChainCode).c_str());
703 printf(" wallet minimum version: %s\n",
704 counts.nWalletMinVersion ? "present" : "none");
705 printf(" keypool entries: %u\n", counts.nPool);
706 printf(" wallet transactions: %u\n", counts.nTransactions);
707 printf(" address book labels: %u\n", counts.nNames);
708 printf(" settings: %u\n", counts.nSettings);
709 printf(" HD metadata records: %u\n",
710 counts.nHDNext + counts.nHDSchema + counts.nHDCoinType +
711 counts.nHDReceiveNext + counts.nHDChangeNext);
712 printf(" database version records: %u\n", counts.nVersion);
713 fflush(stdout);
714
715 if (counts.nMalformed > 0)
716 return 2;
717 return 0;
718}
719
459int CmdEncryptWallet(const std::string& strPassphrase) 720int CmdEncryptWallet(const std::string& strPassphrase)
460{ 721{
461 AttachTerminal(); 722 AttachTerminal();
diff --git a/src/walletcmd.h b/src/walletcmd.h
index 007b8b5..c6342a6 100644
--- a/src/walletcmd.h
+++ b/src/walletcmd.h
@@ -62,6 +62,10 @@ int CmdShowDerived(int nCount);
62// by the installed recovery phrase or still depends on wallet.dat-only keys. 62// by the installed recovery phrase or still depends on wallet.dat-only keys.
63int CmdRecoveryAudit(); 63int CmdRecoveryAudit();
64 64
65// Diagnostic: count wallet.dat record types without printing keys,
66// addresses, transaction ids, labels, or other wallet values.
67int CmdWalletStorageAudit(const std::string& strJsonOut = "");
68
65// Rewrite wallet.dat so private keys and the HD seed are encrypted with the 69// Rewrite wallet.dat so private keys and the HD seed are encrypted with the
66// given passphrase. Returns 0 on success and exits without starting the node. 70// given passphrase. Returns 0 on success and exits without starting the node.
67int CmdEncryptWallet(const std::string& strPassphrase); 71int CmdEncryptWallet(const std::string& strPassphrase);