forked from cheng/wallet
1221 lines
51 KiB
C++
1221 lines
51 KiB
C++
#include "stdafx.h"
|
|
static constexpr char SrcFilename[]{ "src/unit_test.cpp" };
|
|
using ro::msec, ro::msec_since_epoch, ro::bin2hex, ro::to_base64_string, ristretto255::hash,
|
|
ristretto255::hsh, ristretto255::scalar,
|
|
ristretto255::point, ro::base58;
|
|
|
|
class FailureToThrowExpectedException : public MyException {
|
|
public:
|
|
using MyException::MyException;
|
|
FailureToThrowExpectedException() noexcept :
|
|
MyException(FailureToThrowExpectedException_sz) {}
|
|
};
|
|
|
|
|
|
class EndUnitTestOfExceptions : public MyException {
|
|
public:
|
|
using MyException::MyException;
|
|
EndUnitTestOfExceptions() noexcept :
|
|
MyException("\t\tEnd unit test") {}
|
|
};
|
|
|
|
int errorCode{ 0 };
|
|
std::string szError;
|
|
static bool StartUnitTest(void);
|
|
static bool unexpected_unit_test(void);
|
|
static bool(*unit_test_action)(void) { &StartUnitTest };
|
|
static bool (*next_action)(void) { &unexpected_unit_test };
|
|
static bool noaction() { return false; };
|
|
static bool EndUnitTest(void);
|
|
static bool unexpected_unit_test() {
|
|
unit_test_action = &EndUnitTest;
|
|
next_action = unit_test_action;
|
|
szError = "Faulty flow of control in unit test itself";
|
|
ILogError(szError.c_str());
|
|
errorCode = 1005;
|
|
return true;
|
|
}
|
|
|
|
class UnitTestModalHook : public wxModalDialogHook
|
|
{
|
|
protected:
|
|
virtual int Enter(wxDialog* dlg) wxOVERRIDE
|
|
{
|
|
wxLogMessage(
|
|
wxT("Showing %s:%s dialog"),
|
|
dlg->GetClassInfo()->GetClassName(),
|
|
dlg->GetLabel()
|
|
);
|
|
auto x = dlg->GetId();
|
|
switch (x) {
|
|
case myID_ERRORMESSAGE:
|
|
wxLogMessage(wxT("\tError message modal dialog"));
|
|
unit_test_action = next_action;
|
|
wxLogMessage(wxT("\tClosing dialog"));
|
|
return wxID_OK;
|
|
default:
|
|
return wxID_NONE;
|
|
}
|
|
}
|
|
};
|
|
static UnitTestModalHook modal_dialog_hook;
|
|
static bool EndUnitTest() {
|
|
unit_test_action = &noaction;
|
|
modal_dialog_hook.Unregister();
|
|
next_action = &unexpected_unit_test;
|
|
std::string intestbed("Testbed: ");
|
|
try {
|
|
testbed::testbed();
|
|
throw EndUnitTestOfExceptions();
|
|
}
|
|
catch (const EndUnitTestOfExceptions&) {}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = intestbed + e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception& e) {
|
|
errorCode = 1001;
|
|
szError = intestbed + e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = intestbed + sz_unknown_error;
|
|
errorCode = 1000;
|
|
ILogError(szError.c_str());
|
|
}
|
|
if (errorCode == 0) {
|
|
ILogMessage("Passed Unit Test");
|
|
}
|
|
else {
|
|
wxLogMessage(wxT("\nFailed Unit Test\nunit test error %d\n%s"),
|
|
errorCode, _wx(szError.c_str())
|
|
);
|
|
}
|
|
if (singletonApp->m_unit_test && !singletonApp->m_display) {
|
|
singletonFrame->Close(); //Shuts down program immediately.
|
|
// When the program is closed, now or later, will return errorCode.
|
|
}
|
|
return true;
|
|
}
|
|
static bool curve_order(void) {
|
|
unit_test_action = &EndUnitTest;
|
|
next_action = unit_test_action;
|
|
ILogMessage(
|
|
"\tChecking mpz arbitrary precision integer conversions to cryptographic values."
|
|
);
|
|
try {
|
|
scalar max(-1);
|
|
wxString curve_order_as_unreduced_scalar{ _wx(bin2hex(scalar::scOrder)) };
|
|
ro::CompileSizedString<sizeof(scalar) * 2> sz;
|
|
mpz_get_str(sz, 16, mpir::ristretto25519_curve_order);
|
|
wxString curve_order_as_mpir_bigint{ _wx(sz) };
|
|
if (curve_order_as_unreduced_scalar != curve_order_as_mpir_bigint){
|
|
throw MyException("inconsistent curve order", __LINE__, __func__, SrcFilename);
|
|
}
|
|
scalar sc_zero{ 0 };
|
|
scalar sc_one{ 1 };
|
|
scalar sc1{ scalar::random() };
|
|
scalar sc2{ scalar::random() };
|
|
assert(sc1 != sc2);
|
|
assert(sc1<sc2 || sc1>sc2);
|
|
assert(sc1 < scalar::scOrder);
|
|
assert(max < scalar::scOrder);
|
|
assert(max > sc_zero);
|
|
assert(max + sc_one == scalar(0));
|
|
assert(sc_one + max < scalar::scOrder);
|
|
assert(!(sc_one < scalar::scOrder + sc_one));
|
|
assert(sc_one == scalar::scOrder + sc_one);
|
|
assert((sc_one + max < scalar::scOrder));
|
|
assert(sc1.valid());
|
|
assert(!scalar::scOrder.valid());
|
|
assert(!scalar(0).valid());
|
|
bool f = true
|
|
&& sc1 != sc2
|
|
&& sc1<sc2 || sc1>sc2
|
|
&& (sc1 < scalar::scOrder
|
|
&& max < scalar::scOrder
|
|
&& max > sc_zero
|
|
&& max + sc_one == sc_zero
|
|
&& sc_one + max < scalar::scOrder
|
|
&& !(sc_one < scalar::scOrder + sc_one)
|
|
&& sc_one == scalar::scOrder + sc_one
|
|
&& (sc_one + max < scalar::scOrder)
|
|
&& sc1.valid())
|
|
&& !scalar::scOrder.valid()
|
|
&& !scalar(0).valid();
|
|
if (!f)throw MyException("scalar range and validation messed up", __LINE__, __func__, SrcFilename);
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception& e) {
|
|
errorCode = 30;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 30;
|
|
ILogError(szError.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool checkDataConversionsProduceExpected(void){
|
|
unit_test_action = &curve_order;
|
|
next_action = unit_test_action;
|
|
ILogMessage("\tChecking hashing, string conversion, scalar and point generation.");
|
|
try {
|
|
char brownfox[]{ "the quick brown fox jumped over the lazy dog" };
|
|
char* p_brownfox{ brownfox };
|
|
std::array<char, 27>ar_brownfox{ "the quick brown fox jumped" };
|
|
char* p_over{ p_brownfox + 27 };
|
|
wxString wx_over{ p_over };
|
|
*--ar_brownfox.end() = ' '; //fixes the trailing nul appended to ar_brownfox, so that it is
|
|
// no longer a null terminated string.
|
|
wxString wx_jumped(&ar_brownfox[0], std::size(ar_brownfox));
|
|
hsh hsh1;
|
|
hsh hsh2;
|
|
hsh1 << brownfox;
|
|
hsh2 << ar_brownfox << wx_over.ToUTF8();
|
|
hash hash1(hsh1);
|
|
hash hash2(hsh2);
|
|
hash hash3(hsh() << ar_brownfox << wx_over.ToUTF8());
|
|
if (false
|
|
|| hash1 != hash2
|
|
|| hash1 != hash(p_brownfox)
|
|
|| hash1 != hash3
|
|
|| hash(p_brownfox) != hash(hsh() << ar_brownfox << wx_over.ToUTF8())
|
|
|| hash(p_brownfox) != hash(ar_brownfox, wx_over.ToUTF8())
|
|
|| hash(brownfox) != hash(ar_brownfox, p_over)
|
|
)throw MyException(sz_inconsistent_hash, __LINE__, __func__, SrcFilename);
|
|
|
|
{
|
|
scalar scl_b{ scalar::random() };
|
|
point pt_a{ scl_b.timesBase() };
|
|
std::string str_pt_a{ &(base58(pt_a))[0] };
|
|
if (pt_a != base58<point>::bin(str_pt_a.c_str())){
|
|
throw MyException("Round trip from and two base 58 representation failed", __LINE__, __func__, SrcFilename);
|
|
}
|
|
}
|
|
{
|
|
point pt_b;
|
|
try {
|
|
pt_b = base58<point>::bin(
|
|
"galvhAmKLYPjiEUbiDvE54pnKhhdYSxC3G5p9czHPuKaB not base 58 because uses 'l'"
|
|
);
|
|
throw FailureToThrowExpectedException();
|
|
assert(false);
|
|
}
|
|
catch (const NotBase58Exception&) {}
|
|
try {
|
|
pt_b = base58<point>::bin(
|
|
"AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrS wrong checksum"
|
|
);
|
|
throw FailureToThrowExpectedException();
|
|
assert(false);
|
|
}
|
|
catch (const BadStringRepresentationOfCryptoIdException&) {}
|
|
try {
|
|
pt_b = base58<point>::bin(
|
|
"MaevYgZimDjzQWxbnHcNdMqkMVmjWpYEqippTfj9y6U7sM overlong error"
|
|
);
|
|
throw FailureToThrowExpectedException();
|
|
assert(false);
|
|
}
|
|
catch (const OversizeBase58String&) {}
|
|
}
|
|
hash<512>hash_a{ ar_brownfox, wx_over.ToUTF8(), hash1,base58<point>::bin(
|
|
"AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrP"
|
|
) };
|
|
std::string str_hash_a{ &(base58(hash_a))[0] };
|
|
scalar scl_a{ hash_a };
|
|
std::string str_sclr_a = &(base58(scl_a))[0];
|
|
assert(base58<scalar>::bin(str_sclr_a.c_str()) == scl_a);
|
|
point pt_a{ scl_a.timesBase() };
|
|
std::string str_pt_a = &(base58(pt_a))[0];
|
|
assert(base58<point>::bin(str_pt_a.c_str()) == pt_a);
|
|
hash<256> hash_b( hash_a, str_hash_a, scl_a, str_sclr_a, pt_a, wxString(str_pt_a).ToUTF8(), 33, 66ull);
|
|
auto str_b = base58(hash_b).operator std::string();
|
|
if (str_b != "7cTScjKyUtmbvc28BV3ok51szgrQmaBa2YE5HPBcukC9e"
|
|
) {
|
|
throw MyException(
|
|
std::format("unexpected hash of transformations: hash256#{}", str_b),
|
|
__LINE__, __func__, SrcFilename);
|
|
}
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception& e) {
|
|
errorCode = 27;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 26;
|
|
ILogError(szError.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool CheckForUtfEnvironment(void) {
|
|
ILogMessage("\tChecking for UTF-8 locale. ☺");
|
|
try {
|
|
bool utfEnvironment{ true };
|
|
std::string cum{};
|
|
#ifdef _WIN32
|
|
{
|
|
auto ACP{ GetACP() };
|
|
// Check that windows thinks this is UTF8
|
|
utfEnvironment = utfEnvironment && (ACP == 65001);
|
|
if (!utfEnvironment) {
|
|
cum += std::format("current code page {}, —should be 65001☹, ", ACP);
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef __linux
|
|
{
|
|
//assume that in startup we have done setlocale(LC_CTYPE, "something.utf-8")
|
|
// LC_CTYPE=UTF-8)
|
|
// Problem is that UTF-8 is not necessarily a valid value. Sometimes
|
|
// it has to be something like en_US.UTF-8
|
|
// If it does not end in UTF-8, needs to be setlocale to C.UTF-8
|
|
// You don't know what locales are available. C.UTF-8 is usually
|
|
// available, but anything of the form "something.UTF-8" or "something.utf-8"
|
|
// should work.
|
|
if (nl_langinfo(CODESET)) != "UFT-8"){
|
|
utfEnvironment = false;
|
|
cum += std::format("current code page {}, —should be UTF-8☹, ", nl_langinfo(CODESET));
|
|
// Any linux C or C++ program should automatically and always find itself by default
|
|
// in the UTF-8 codeset on linux these days.
|
|
\*"7.2 POSIX Locale
|
|
Conforming systems shall provide a POSIX locale, also known as the C locale.In POSIX.1 the requirements for the POSIX locale are more extensive than the requirements for the C locale as specified in the ISO C standard.However, in a conforming POSIX implementation, the POSIX locale and the C locale are identical.The behavior of standard utilities and functions in the POSIX locale shall be as if the locale was defined via the localedef utility with input data from the POSIX locale tables in Locale Definition.
|
|
|
|
For C - language programs, the POSIX locale shall be the default locale when the setlocale() function is not called"*/"
|
|
// Unicode sort and search that treats case as significant is not globally defined, but is language dependent. Where
|
|
// consistent behavior is required, go posix, as sqlite does. (andles case only for ABC...Z) The ICU extension, which can be loaded
|
|
// into sqlite, provides no end of additional, difficult to comprehend, behavior, which however tends to be language specific
|
|
// you have to tell it which language it is dealing with. So useful only where we do not care about consistency.
|
|
// Unix evades the endless depths of this problem by cheerfully making everything case sensitive and using binary collate.
|
|
}
|
|
}
|
|
#endif
|
|
auto FontEncoding{ wxLocale::GetSystemEncoding() };
|
|
// check that wxWidgets thinks this is UTF8
|
|
utfEnvironment = utfEnvironment && (FontEncoding == wxFONTENCODING_UTF8);
|
|
if (!utfEnvironment) {
|
|
cum += std::format("swxFontEncoding {}, —should be {} ☹",
|
|
(int)FontEncoding,
|
|
(int)wxFONTENCODING_UTF8
|
|
);
|
|
throw MyException(cum, __LINE__, __func__, SrcFilename);
|
|
}
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception& e) {
|
|
errorCode = 24;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 24;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &checkDataConversionsProduceExpected;
|
|
return true;
|
|
}
|
|
static bool Waiting_for_delayed_dummy_error_message(void) {
|
|
static int count(12);
|
|
if ((--count)) {
|
|
ILogMessage("\t\tWaiting for second dummy error message");
|
|
}
|
|
else {
|
|
errorCode = 21;
|
|
szError = "Delayed dummy error message UI failed show up";
|
|
ILogError(szError.c_str());
|
|
unit_test_action = next_action;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
class UnitTestOfException : public MyException {
|
|
public:
|
|
using MyException::MyException;
|
|
UnitTestOfException() noexcept :
|
|
MyException("\t\tNot a real error, merely at test of error handling UI") {}
|
|
};
|
|
|
|
static bool TestDelayedErrorDialog(void) {
|
|
unit_test_action = &Waiting_for_delayed_dummy_error_message;
|
|
next_action = &CheckForUtfEnvironment;
|
|
ILogMessage("\t\tthrowing exception");
|
|
throw UnitTestOfException(); //If we throw exception before setting the next unit test,
|
|
// this unit test gets endless recalled.
|
|
return true;
|
|
}
|
|
|
|
static bool Waiting_for_dummy_error_message(void) {
|
|
static int count(12);
|
|
if ((--count)) {
|
|
ILogMessage("\t\tWaiting for dummy error message");
|
|
}
|
|
else {
|
|
errorCode = 21;
|
|
szError = "Dummy error message UI failed show up";
|
|
ILogError(szError.c_str());
|
|
unit_test_action = next_action;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
static bool TestErrorDialog(void) {
|
|
unit_test_action = &Waiting_for_dummy_error_message;
|
|
next_action= &TestDelayedErrorDialog;
|
|
ILogMessage("\tTest error handling UI.");
|
|
ILogMessage("\t\tposting delayed error message\n\t\tas if inside an exception handler or destructor");
|
|
auto event = new wxCommandEvent(wxEVT_MENU, myID_ERRORMESSAGE);
|
|
wxQueueEvent(singletonFrame->GetMenuBar(), event);
|
|
// wxQueueEvent(singletonApp, event);
|
|
unit_test_action = &Waiting_for_dummy_error_message;
|
|
return true;
|
|
}
|
|
|
|
static bool WalletCreationUI(void) {
|
|
// singletonFrame->menu_OnNewWallet.queue_event();
|
|
unit_test_action = &TestErrorDialog;
|
|
return true;
|
|
}
|
|
|
|
static bool OpenWallet(void) {
|
|
try {
|
|
/* If LastUsed is the empty string, check if default filename exists. If it exists, set
|
|
Last Used to default file name
|
|
If LastUsed is not the empty string, or no longer the empty string, attempt to open
|
|
indicated file. If open fails, or reading the opened file produces bad results, abort with
|
|
exception
|
|
If LastUsed is still the empty string, attempt to create default filename. If creation fails,
|
|
abort with exception. If it succeeds, set LastUsed to default filename.
|
|
The exception in unit test should simply generate an error message, but if run during
|
|
initialization, should bring up the more complex UI for constructing or selecting your
|
|
wallet file.*/
|
|
ILogMessage("\tWallet file");
|
|
bool fWalletNameOk{ false };
|
|
wxStandardPaths& StandardPaths(wxStandardPaths::Get());
|
|
StandardPaths.UseAppInfo(3);
|
|
wxFileName DefaultSqlite(StandardPaths.GetUserLocalDataDir(), "default.wallet");
|
|
wxFileName& LastUsedSqlite(singletonFrame->m_LastUsedWallet);
|
|
wxLogMessage(wxT("\t\tLastUsed=\"%s\""), LastUsedSqlite.GetFullPath());
|
|
if (!LastUsedSqlite.IsOk() || !LastUsedSqlite.HasName() || !LastUsedSqlite.HasExt()) {
|
|
wxLogMessage(wxT("\t\tDefault=\"%s\""), DefaultSqlite.GetFullPath());
|
|
assert(DefaultSqlite.IsOk() && DefaultSqlite.HasName() && DefaultSqlite.HasExt());
|
|
if (DefaultSqlite.FileExists()) {
|
|
LastUsedSqlite = DefaultSqlite;
|
|
singletonFrame->m_LastUsedWallet = LastUsedSqlite;
|
|
fWalletNameOk = true;
|
|
}
|
|
}
|
|
else fWalletNameOk = true;
|
|
std::unique_ptr<ISqlite3> db;
|
|
if (fWalletNameOk) {
|
|
if (!LastUsedSqlite.FileExists()) throw MyException("Expected wallet file not found", __LINE__, __func__, SrcFilename);
|
|
db.reset(Sqlite3_open(LastUsedSqlite.GetFullPath().ToUTF8()));
|
|
sql_read_from_misc read_from_misc(db.get());
|
|
if(!read_from_misc(1) || read_from_misc.value<int64_t>() != WALLET_FILE_IDENTIFIER)throw MyException("Unrecognizable wallet file format", __LINE__, __func__, SrcFilename);
|
|
if(!read_from_misc(2) || read_from_misc.value<int64_t>() != WALLET_FILE_SCHEMA_VERSION_0_0)throw MyException("Unrecognized wallet schema", __LINE__, __func__, SrcFilename);
|
|
if (!read_from_misc(4)) throw MyException("Mastersecret missing", __LINE__, __func__, SrcFilename);
|
|
ristretto255::CMasterSecret MasterSecret(*read_from_misc.value<ristretto255::scalar>());
|
|
ILogMessage(std::format("\t\tmaster secret: #{}", ro::base58(MasterSecret).operator const char *()).c_str());
|
|
ro::sql read_keys(db.get(), R"|(SELECT * FROM "Keys" LIMIT 5;)|");
|
|
sql_read_name read_name(db.get());
|
|
// db.reset(nullptr);// Force error of premature destruction of Isqlite3
|
|
while (read_keys.step() == Icompiled_sql::ROW) {
|
|
auto pubkey = *read_keys.column<ristretto255::point>(1);
|
|
auto id = read_keys.column<int>(2);
|
|
auto use = read_keys.column<int>(3);
|
|
if (use != 1)throw MyException(sz_unknown_secret_key_algorithm, __LINE__, __func__, SrcFilename);
|
|
if (!read_name(id)) throw MyException(sz_no_corresponding_entry, __LINE__, __func__, SrcFilename);
|
|
const char* name = read_name.name();
|
|
if(MasterSecret(name).timesBase()!=pubkey)throw MyException(R"|(Public key of name fails to correspond)|", __LINE__, __func__, SrcFilename);
|
|
wxLogMessage(wxT("\t\t\"%s\" has expected public key #%s"), name, (wxString)(ro::base58(pubkey).operator const char* ()));
|
|
}
|
|
}
|
|
else {
|
|
// At this point in the code the filename LastUsedSqlite is a bad filename, normally the empty string, and the default wallet file does not exist in the default location.
|
|
// Construct default wallet and filename*/
|
|
wxFileName path{ StandardPaths.GetUserLocalDataDir() };
|
|
try {
|
|
// Disk operations to create wallet, which may throw.
|
|
// This try/catch block exists to catch disk io issues.
|
|
if (!path.DirExists())path.Mkdir();
|
|
if (!DefaultSqlite.DirExists())DefaultSqlite.Mkdir();
|
|
db.reset(Sqlite3_create(DefaultSqlite.GetFullPath().ToUTF8()));
|
|
db->exec(R"|(
|
|
PRAGMA journal_mode = WAL;
|
|
PRAGMA synchronous = 1;
|
|
BEGIN IMMEDIATE TRANSACTION;
|
|
CREATE TABLE "Keys"(
|
|
"ROWID" INTEGER PRIMARY KEY,
|
|
"pubkey" BLOB NOT NULL UNIQUE,
|
|
"id" integer NOT NULL,
|
|
"use" INTEGER NOT NULL
|
|
) STRICT;
|
|
|
|
CREATE UNIQUE INDEX i_pubkey ON Keys (pubkey);
|
|
CREATE UNIQUE INDEX i_id ON Keys (use, id);
|
|
|
|
CREATE TABLE "Names"(
|
|
"ROWID" INTEGER PRIMARY KEY,
|
|
"name" TEXT NOT NULL UNIQUE
|
|
) STRICT;
|
|
|
|
CREATE UNIQUE INDEX i_names ON Names (name);
|
|
|
|
CREATE TABLE "Misc"(
|
|
"ROWID" INTEGER PRIMARY KEY,
|
|
"m" ANY
|
|
) STRICT;
|
|
COMMIT;
|
|
|
|
BEGIN IMMEDIATE TRANSACTION;
|
|
CREATE VIEW UserZookoIDs AS
|
|
SELECT
|
|
"Names".name AS name,
|
|
"Keys".pubkey AS pubkey
|
|
FROM "Names" INNER JOIN "Keys"
|
|
ON "Names"."ROWID"="Keys"."id" AND "Keys"."use"=1
|
|
ORDER BY LOWER("name"), "name"
|
|
COLLATE BINARY;
|
|
COMMIT;
|
|
|
|
BEGIN IMMEDIATE TRANSACTION;
|
|
CREATE TRIGGER InsertUserZookoID INSTEAD OF INSERT ON UserZookoIDs FOR EACH ROW BEGIN
|
|
INSERT OR FAIL INTO "Names" VALUES(
|
|
NULL,
|
|
NEW."name"
|
|
);
|
|
INSERT OR FAIL INTO "Keys" VALUES(
|
|
NULL,
|
|
NEW."pubkey",
|
|
last_insert_rowid(),
|
|
1
|
|
);
|
|
END;
|
|
|
|
CREATE TRIGGER DeleteUserZookoID INSTEAD OF DELETE ON UserZookoIDs FOR EACH ROW BEGIN
|
|
DELETE FROM "Keys" WHERE "Keys"."pubkey" = OLD."pubkey";
|
|
DELETE FROM "Names" WHERE "Names"."name" = OLD."name";
|
|
END;
|
|
COMMIT;
|
|
)|");
|
|
LastUsedSqlite = DefaultSqlite;
|
|
singletonFrame->m_LastUsedWallet = LastUsedSqlite;
|
|
wxLogMessage(wxT("\t\tConstructing default wallet %s"), DefaultSqlite.GetFullPath());
|
|
// We now have a working wallet file with no valid data. Attempting to create a strong random secret, a name, and public and private keys for that name.
|
|
|
|
wxLogMessage(wxT("\t\tGenerating random 128 bit wallet secret"));
|
|
auto text_secret{ DeriveTextSecret(ristretto255::scalar::random(), 1) };
|
|
ro::msec start_time{ ro::msec_since_epoch() };
|
|
ristretto255::CMasterSecret MasterSecret(DeriveStrongSecret(&text_secret[0]) );
|
|
decltype(start_time) end_time{ ro::msec_since_epoch() };
|
|
wxLogMessage(wxT("\t\tStrong secret derivation took %d milliseconds"), (end_time - start_time).count());
|
|
sql_update_to_misc update_to_misc(db.get());
|
|
update_to_misc(1, WALLET_FILE_IDENTIFIER);
|
|
update_to_misc(2, WALLET_FILE_SCHEMA_VERSION_0_0);
|
|
update_to_misc(3, &text_secret[0]);
|
|
|
|
update_to_misc(4, MasterSecret);
|
|
sql_insert_name insert_name(db.get());
|
|
const char cpsz[]{ "Unit Tester" };
|
|
insert_name(cpsz, MasterSecret(cpsz).timesBase());
|
|
}
|
|
catch (const MyException & e) {
|
|
ILogError(R"|(Failed to create or failed to properly initialize wallet)|");
|
|
errorCode = 20;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
} // End of wallet creation branch
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception & e) {
|
|
errorCode = 20;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 20;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &WalletCreationUI;
|
|
return true;
|
|
}
|
|
|
|
static bool StandardPaths(void) {
|
|
wxStandardPaths& StandardPaths{ wxStandardPaths::Get() };
|
|
StandardPaths.UseAppInfo(3);
|
|
ILogMessage("\tStandard paths");
|
|
wxLogMessage("\t\twxStandardPaths::GetUserLocalDataDir()\t %s", StandardPaths.GetUserLocalDataDir());
|
|
wxLogMessage("\t\twxStandardPaths::GetUserDataDir() \t %s", StandardPaths.GetUserDataDir());
|
|
wxLogMessage("\t\twxStandardPaths::GetLocalDataDir() \t %s", StandardPaths.GetLocalDataDir());
|
|
unit_test_action = &OpenWallet;
|
|
return true;
|
|
}
|
|
|
|
static bool TestErrorHandlingUI(void) {
|
|
next_action = unit_test_action = &StandardPaths;
|
|
throw UnitTestOfException(); //If we throw exception before setting the next unit test, this unit test gets endless recalled.
|
|
return true;
|
|
}
|
|
|
|
static uintmax_t shared_secrets_per_second{ 0 };
|
|
|
|
namespace ristretto255 {
|
|
static bool TestSecretGeneration(void) {
|
|
try {
|
|
ILogMessage("\tTest pseudorandom generation of secrets from scalar(7).");
|
|
auto text_secret{ DeriveTextSecret(scalar(7), 1) };
|
|
if (text_secret == decltype(text_secret){"Rmc mLSu mDk DhfV 9gBK kKj"}) {
|
|
ILogMessage("\t\tObtained expected text secret from scalar(7)");
|
|
}
|
|
else {
|
|
errorCode = 18;
|
|
szError = "Fail\tUnexpected text secret from scalar(7)";
|
|
ILogError(szError.c_str());
|
|
}
|
|
if (std::popcount(0x0123456789ABCDEFu) != 0x20)throw MyException(sz_bitcount_incorrect, __LINE__, __func__, SrcFilename);
|
|
if (rounded_log2(0x345678u) != 22) throw MyException(sz_rounded_log2_incorrect, __LINE__, __func__, SrcFilename);
|
|
{ uint64_t v{ 0x123456789ABCDEFu };
|
|
for (unsigned int i = 0; i <= 64; i++) {
|
|
if (i != std::countr_zero(v))throw MyException(sz_count_of_trailing_zero_bits_incorrect, __LINE__, __func__, SrcFilename);
|
|
v <<= 1;
|
|
}
|
|
}
|
|
if (!(singletonApp->m_quick_unit_test)){
|
|
auto start_time{ std::chrono::high_resolution_clock::now() };
|
|
auto s1{ DeriveStrongSecret(&text_secret[0]) };
|
|
auto end_time{ std::chrono::high_resolution_clock::now() };
|
|
if (s1 == scalar({ 0x59, 0xf6, 0x73, 0xb4, 0xa0, 0xc7, 0x0d, 0x8b, 0x51, 0xe5, 0x87, 0x7c, 0xf5, 0xd7, 0x6f, 0x55, 0x31, 0xa7, 0x0b, 0x14, 0x28, 0x54, 0x97, 0x08, 0x9f, 0x27, 0x83, 0xe1, 0xc7, 0x5f, 0x55, 0x0f })) {
|
|
wxLogMessage("\t\tObtained expected strong scalar secret from scalar(7)");
|
|
}
|
|
else {
|
|
errorCode = 17;
|
|
szError = "Fail\tUnexpected strong scalar secret from text secret";
|
|
ILogError(szError.c_str());
|
|
}
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
|
|
wxLogMessage("\t\tStrong secret derivation took %lld microseconds", time_taken.count());
|
|
uintmax_t strengthening = time_taken.count() * shared_secrets_per_second / 1000000;
|
|
wxLogMessage("\t\tslows brute force password search by a factor of %ju", strengthening);
|
|
wxLogMessage("\t\tstrengthens password by %u bits",
|
|
static_cast<unsigned int>(rounded_log2(static_cast<uint32_t>(strengthening))));
|
|
auto s2{ DeriveSecret(s1, 1) };
|
|
if (s2 == scalar({ 0x02, 0x98, 0x0e, 0x7d, 0x12, 0xe8, 0x23, 0xeb, 0xa2, 0x08, 0xa8, 0x51, 0x73, 0x27, 0x96, 0xc6, 0xb9, 0x12, 0x60, 0x27, 0x25, 0x24, 0x75, 0xa3, 0x4e, 0x2a, 0x6a, 0x44, 0x56, 0xab, 0x95, 0x06 })) {
|
|
ILogMessage("\t\tObtained expected fast scalar secret from strong scalar secret");
|
|
}
|
|
else {
|
|
errorCode = 16;
|
|
szError = "Fail\tUnexpected fast scalar secret from strong scalar secret";
|
|
ILogError(szError.c_str());
|
|
}
|
|
}
|
|
else {
|
|
ILogMessage("\t\tSkipping test of strong secret generation in quick unit test, because strong secret generation is deliberately slow.");
|
|
}
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception & e) {
|
|
errorCode = 16;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 16;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &TestErrorHandlingUI;
|
|
return true;
|
|
}
|
|
|
|
static bool TestShareSecretGenerationSpeed(void) {
|
|
ILogMessage("\tTest shared secret generation speed.");
|
|
try {
|
|
// throw MyException("fake failure to test failure handling", __LINE__, __func__, SrcFilename);
|
|
unsigned int secrets{ 10 };
|
|
scalar a{ scalar::random() };
|
|
scalar b{ scalar::random() };
|
|
scalar c;
|
|
auto B{ a.timesBase() };
|
|
decltype(B) A, C;
|
|
auto start_time{ std::chrono::high_resolution_clock::now() };
|
|
for (unsigned int i{ secrets }; i; i--) {
|
|
c = a + b;
|
|
C = c * B;
|
|
a = b + c;
|
|
A = a * C;
|
|
b = c + a;
|
|
B = b * A;
|
|
}
|
|
auto end_time{ std::chrono::high_resolution_clock::now() };
|
|
if (!B.valid()) { throw MyException("Unexpected invalid scalar", __LINE__, __func__, SrcFilename);
|
|
}
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
|
|
wxLogMessage("\t\t%d shared secrets from public and private keys took %lld microseconds", secrets * 3, time_taken.count());
|
|
if (time_taken.count()) {
|
|
shared_secrets_per_second = secrets * 3000000 / time_taken.count();
|
|
wxLogMessage("\t\t in one second, can generate %ju shared secrets",
|
|
shared_secrets_per_second
|
|
);
|
|
}
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception& e) {
|
|
errorCode = 23;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 23;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &TestSecretGeneration;
|
|
return true;
|
|
}
|
|
|
|
bool operator ==(const std::span<byte>& p, const std::span<byte>& q) {
|
|
bool breturn{ true };
|
|
for (auto xq = q.begin(); auto xp:p) {
|
|
if (xp != *xq++) {
|
|
breturn = false;
|
|
break;
|
|
}
|
|
}
|
|
return breturn;
|
|
}
|
|
|
|
bool operator !=(const std::span<byte>& p, const std::span<byte>& q) {
|
|
bool breturn{ false };
|
|
for (auto xq = q.begin(); auto xp:p) {
|
|
if (xp != *xq++) {
|
|
breturn = true;
|
|
break;
|
|
}
|
|
}
|
|
return breturn;
|
|
}
|
|
|
|
static bool TestSignatures(void) {
|
|
try {
|
|
ILogMessage("\tTest Schnorr signatures.");
|
|
//Construct Ann's secret and public keys:
|
|
scalar sclrAnnSecretKey{ scalar::random() };
|
|
point ptAnnPublicKey{ sclrAnnSecretKey.timesBase() };
|
|
// Ann signs the string "hello"
|
|
std::array<char, 6> szHello{ "hello" };
|
|
// Ann creates signature.
|
|
signed_text sig(
|
|
sclrAnnSecretKey, // Signer's secret key
|
|
ptAnnPublicKey, // Signer's public key
|
|
szHello // Text to be signed.
|
|
);
|
|
auto start_time{ std::chrono::high_resolution_clock::now() };
|
|
if (sig.verify())
|
|
{
|
|
auto end_time{ std::chrono::high_resolution_clock::now() };
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
|
|
wxLogMessage("\t\tverification of Schnorr signature took %lld microseconds", time_taken.count());
|
|
|
|
ILogMessage("\t\tSchnorr Signature valid");
|
|
}
|
|
else {
|
|
errorCode = 9;
|
|
szError = "Fail\tInvalid signature.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
// Testing signature vector, using a low, easily guessed, secret key
|
|
sig = signed_text(scalar(0x7F), scalar(0x7F).timesBase(), szHello);
|
|
assert(sig.verify());
|
|
|
|
scalar sig_test_vector(
|
|
{
|
|
0x7b, 0x28, 0xd4, 0x49, 0x99, 0xed, 0xb2, 0x40,
|
|
0x61, 0x4d, 0x22, 0x96, 0x25, 0x78, 0xb6, 0x3b,
|
|
0x5f, 0x72, 0xf7, 0xde, 0x99, 0x29, 0xe2, 0x50,
|
|
0x41, 0xa5, 0xcd, 0xaa, 0x5b, 0x0b, 0xff, 0x0d,
|
|
}
|
|
);
|
|
if (sig_test_vector == sig.c) {
|
|
ILogMessage("\t\tproduced expected signature test vector");
|
|
}
|
|
else {
|
|
errorCode = 14;
|
|
ILogMessage(("\t\tsig of test vector: " + to_base64_string(sig.c)).c_str());
|
|
ILogMessage(("\texpected sig of test vector: " + to_base64_string(sig_test_vector)).c_str());
|
|
szError = "Fail\tSignature different from expected test vector.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
std::array<uint8_t, 6>expected_serialized_int{ 0x88,0x94,0x92,0x8d,0x88,0x45 };
|
|
bool f{ true };
|
|
// f = f && scalar(ro::deserialize<32>(ro::serialize(sig_test_vector))) == sig_test_vector;
|
|
// f = f && scalar(ro::deserialize<32>(&ro::serialize(sig_test_vector)[0])) == sig_test_vector;
|
|
f = f && ro::serialize(0x21U) == ro::serialize(int8_t(0x21));
|
|
f = f && ro::serialize(0x21) == ro::serialize(int8_t(0x21));
|
|
f = f && ro::serialize(-60) == ro::serialize(int8_t(-60));
|
|
f = f && ro::serialize(-8107) == ro::serialize(int16_t(-8107));
|
|
f = f && ro::serialize(0x41U).size() == 1;
|
|
f = f && ro::serialize(0x2142U).size() == 2;
|
|
f = f && ro::serialize(0x4142U).size() == 3;
|
|
f = f && ro::serialize(0x4142434445464748U).size() == 9;
|
|
f = f && ro::serialize(0x8142434445464748U).size() == 10;
|
|
f = f && ro::serialize(0x4142434445U) == expected_serialized_int;
|
|
f = f && ro::deserialize<uint64_t>(ro::serialize(0x4142434445U)) == 0x4142434445U;
|
|
f = f && ro::deserialize<uint64_t>(ro::serialize(0xFF41424344454647U)) == 0xFF41424344454647U;
|
|
f = f && ro::deserialize<int64_t>(ro::serialize(0x4142434445)) == 0x4142434445;
|
|
f = f && ro::deserialize<int64_t>(ro::serialize(0xFF41424344454647)) == 0xFF41424344454647;
|
|
f = f && ro::deserialize<int64_t>(ro::serialize(int32_t(-8107))) == -8107;
|
|
if (f) {
|
|
ILogMessage("\t\tro:serialize ro:deserialize on integers produced expected result");
|
|
}
|
|
else {
|
|
errorCode = 15;
|
|
szError = "Fail\tro:deserialize ro:deserialize on integers produced unexpected results.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
wxLogMessage(wxT("\tLibsodium constants\n\t\tsizeof(crypto_secretstream_xchacha20poly1305_state)=%d"), int(sizeof(crypto_secretstream_xchacha20poly1305_state)));
|
|
wxLogMessage(wxT("\t\tcrypto_generichash_KEYBYTES=%d"), crypto_generichash_KEYBYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_secretstream_xchacha20poly1305_KEYBYTES=%d"), crypto_secretstream_xchacha20poly1305_KEYBYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_secretstream_xchacha20poly1305_HEADERBYTES=%d"), crypto_secretstream_xchacha20poly1305_HEADERBYTES);
|
|
wxLogMessage(wxT("\t\tchecksum size == crypto_secretstream_xchacha20poly1305_ABYTES ==%d"), crypto_secretstream_xchacha20poly1305_ABYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_OPSLIMIT_MIN ==\t%08x"), crypto_pwhash_OPSLIMIT_MIN);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_OPSLIMIT_MODERATE ==\t%08x"), crypto_pwhash_OPSLIMIT_MODERATE);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_OPSLIMIT_SENSITIVE ==\t%08x"), crypto_pwhash_OPSLIMIT_SENSITIVE);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_OPSLIMIT_MAX ==\t%08x"), crypto_pwhash_OPSLIMIT_MAX);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_MEMLIMIT_MIN ==\t%08x"), crypto_pwhash_MEMLIMIT_MIN);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_MEMLIMIT_MODERATE==%08x"), crypto_pwhash_MEMLIMIT_MODERATE);
|
|
wxLogMessage(wxT("\t\tcrypto_pwhash_MEMLIMIT_SENSITIVE==\t%08x"), crypto_pwhash_MEMLIMIT_SENSITIVE);
|
|
wxLogMessage(wxT("\t\tcrypto_auth_BYTES ==\t\t%08x"), crypto_auth_BYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_stream_chacha20_NONCEBYTES\t%08x"), crypto_stream_chacha20_NONCEBYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_stream_chacha20_KEYBYTES ==\t%08x"), crypto_stream_chacha20_KEYBYTES);
|
|
wxLogMessage(wxT("\t\tchacha20 is 2^(256+128) 512 bit pseudo random blocks"));
|
|
wxLogMessage(wxT("\t\tcrypto_stream_xchacha20_NONCEBYTES\t%08x"), crypto_stream_xchacha20_NONCEBYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_stream_xchacha20_KEYBYTES ==\t%08x"), crypto_stream_chacha20_KEYBYTES);
|
|
wxLogMessage(wxT("\t\txchacha20 is 2^512 512 bit pseudo random blocks"));
|
|
wxLogMessage(wxT("\t\tmin hash size is %d bits"), crypto_generichash_BYTES_MIN * 8);
|
|
wxLogMessage(wxT("\t\tmax hash size is %d bits"), crypto_generichash_BYTES_MAX * 8);
|
|
wxLogMessage(wxT("\t\tcrypto_shorthash_BYTES\t== %08x"), crypto_shorthash_BYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_shorthash_KEYBYTES\t== %08x"), crypto_shorthash_KEYBYTES);
|
|
wxLogMessage(wxT("\t\tcrypto_auth_BYTES\t\t== %08x"), crypto_auth_BYTES);
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception & e) {
|
|
errorCode = 15;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 15;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &TestShareSecretGenerationSpeed;
|
|
return true;
|
|
}
|
|
|
|
static std::array<point, 0x10>ristretto_test_vector{
|
|
point({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }),
|
|
point({0xe2, 0xf2, 0xae, 0x0a, 0x6a, 0xbc, 0x4e, 0x71, 0xa8, 0x84, 0xa9, 0x61, 0xc5, 0x00, 0x51, 0x5f, 0x58, 0xe3, 0x0b, 0x6a, 0xa5, 0x82, 0xdd, 0x8d, 0xb6, 0xa6, 0x59, 0x45, 0xe0, 0x8d, 0x2d, 0x76}),
|
|
point({ 0x6a, 0x49, 0x32, 0x10, 0xf7, 0x49, 0x9c, 0xd1, 0x7f, 0xec, 0xb5, 0x10, 0xae, 0x0c, 0xea, 0x23, 0xa1, 0x10, 0xe8, 0xd5, 0xb9, 0x01, 0xf8, 0xac, 0xad, 0xd3, 0x09, 0x5c, 0x73, 0xa3, 0xb9, 0x19}),
|
|
point({0x94, 0x74, 0x1f, 0x5d, 0x5d, 0x52, 0x75, 0x5e, 0xce, 0x4f, 0x23, 0xf0, 0x44, 0xee, 0x27, 0xd5, 0xd1, 0xea, 0x1e, 0x2b, 0xd1, 0x96, 0xb4, 0x62, 0x16, 0x6b, 0x16, 0x15, 0x2a, 0x9d, 0x02, 0x59}),
|
|
point({0xda, 0x80, 0x86, 0x27, 0x73, 0x35, 0x8b, 0x46, 0x6f, 0xfa, 0xdf, 0xe0, 0xb3, 0x29, 0x3a, 0xb3, 0xd9, 0xfd, 0x53, 0xc5, 0xea, 0x6c, 0x95, 0x53, 0x58, 0xf5, 0x68, 0x32, 0x2d, 0xaf, 0x6a, 0x57}),
|
|
point({0xe8, 0x82, 0xb1, 0x31, 0x01, 0x6b, 0x52, 0xc1, 0xd3, 0x33, 0x70, 0x80, 0x18, 0x7c, 0xf7, 0x68, 0x42, 0x3e, 0xfc, 0xcb, 0xb5, 0x17, 0xbb, 0x49, 0x5a, 0xb8, 0x12, 0xc4, 0x16, 0x0f, 0xf4, 0x4e}),
|
|
point({0xf6, 0x47, 0x46, 0xd3, 0xc9, 0x2b, 0x13, 0x05, 0x0e, 0xd8, 0xd8, 0x02, 0x36, 0xa7, 0xf0, 0x00, 0x7c, 0x3b, 0x3f, 0x96, 0x2f, 0x5b, 0xa7, 0x93, 0xd1, 0x9a, 0x60, 0x1e, 0xbb, 0x1d, 0xf4, 0x03}),
|
|
point({0x44, 0xf5, 0x35, 0x20, 0x92, 0x6e, 0xc8, 0x1f, 0xbd, 0x5a, 0x38, 0x78, 0x45, 0xbe, 0xb7, 0xdf, 0x85, 0xa9, 0x6a, 0x24, 0xec, 0xe1, 0x87, 0x38, 0xbd, 0xcf, 0xa6, 0xa7, 0x82, 0x2a, 0x17, 0x6d}),
|
|
point({0x90, 0x32, 0x93, 0xd8, 0xf2, 0x28, 0x7e, 0xbe, 0x10, 0xe2, 0x37, 0x4d, 0xc1, 0xa5, 0x3e, 0x0b, 0xc8, 0x87, 0xe5, 0x92, 0x69, 0x9f, 0x02, 0xd0, 0x77, 0xd5, 0x26, 0x3c, 0xdd, 0x55, 0x60, 0x1c}),
|
|
point({0x02, 0x62, 0x2a, 0xce, 0x8f, 0x73, 0x03, 0xa3, 0x1c, 0xaf, 0xc6, 0x3f, 0x8f, 0xc4, 0x8f, 0xdc, 0x16, 0xe1, 0xc8, 0xc8, 0xd2, 0x34, 0xb2, 0xf0, 0xd6, 0x68, 0x52, 0x82, 0xa9, 0x07, 0x60, 0x31}),
|
|
point({0x20, 0x70, 0x6f, 0xd7, 0x88, 0xb2, 0x72, 0x0a, 0x1e, 0xd2, 0xa5, 0xda, 0xd4, 0x95, 0x2b, 0x01, 0xf4, 0x13, 0xbc, 0xf0, 0xe7, 0x56, 0x4d, 0xe8, 0xcd, 0xc8, 0x16, 0x68, 0x9e, 0x2d, 0xb9, 0x5f}),
|
|
point({0xbc, 0xe8, 0x3f, 0x8b, 0xa5, 0xdd, 0x2f, 0xa5, 0x72, 0x86, 0x4c, 0x24, 0xba, 0x18, 0x10, 0xf9, 0x52, 0x2b, 0xc6, 0x00, 0x4a, 0xfe, 0x95, 0x87, 0x7a, 0xc7, 0x32, 0x41, 0xca, 0xfd, 0xab, 0x42}),
|
|
point({0xe4, 0x54, 0x9e, 0xe1, 0x6b, 0x9a, 0xa0, 0x30, 0x99, 0xca, 0x20, 0x8c, 0x67, 0xad, 0xaf, 0xca, 0xfa, 0x4c, 0x3f, 0x3e, 0x4e, 0x53, 0x03, 0xde, 0x60, 0x26, 0xe3, 0xca, 0x8f, 0xf8, 0x44, 0x60}),
|
|
point({0xaa, 0x52, 0xe0, 0x00, 0xdf, 0x2e, 0x16, 0xf5, 0x5f, 0xb1, 0x03, 0x2f, 0xc3, 0x3b, 0xc4, 0x27, 0x42, 0xda, 0xd6, 0xbd, 0x5a, 0x8f, 0xc0, 0xbe, 0x01, 0x67, 0x43, 0x6c, 0x59, 0x48, 0x50, 0x1f}),
|
|
point({0x46, 0x37, 0x6b, 0x80, 0xf4, 0x09, 0xb2, 0x9d, 0xc2, 0xb5, 0xf6, 0xf0, 0xc5, 0x25, 0x91, 0x99, 0x08, 0x96, 0xe5, 0x71, 0x6f, 0x41, 0x47, 0x7c, 0xd3, 0x00, 0x85, 0xab, 0x7f, 0x10, 0x30, 0x1e}),
|
|
point({0xe0, 0xc4, 0x18, 0xf7, 0xc8, 0xd9, 0xc4, 0xcd, 0xd7, 0x39, 0x5b, 0x93, 0xea, 0x12, 0x4f, 0x3a, 0xd9, 0x90, 0x21, 0xbb, 0x68, 0x1d, 0xfc, 0x33, 0x02, 0xa9, 0xd9, 0x9a, 0x2e, 0x53, 0xe6, 0x4e})
|
|
};
|
|
|
|
static bool UnitTest(void) {
|
|
try {
|
|
ILogMessage("\tBegin ristretto255 test vectors.");
|
|
|
|
bool fFailed{ false };
|
|
int cryptoError{ 0 };
|
|
for (auto const& x : ristretto_test_vector) {
|
|
if (!x.valid()) {
|
|
cryptoError = 9;
|
|
}
|
|
}
|
|
if (cryptoError) {
|
|
errorCode = cryptoError;
|
|
szError = "Fail\tInvalid test vectors.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
auto start_time{ std::chrono::high_resolution_clock::now() };
|
|
auto sclr_a = scalar::random();
|
|
auto sclr_b = scalar::random();
|
|
if (sclr_a == sclr_b)throw MyException("random not very random", __LINE__, __func__, SrcFilename);
|
|
auto sclr_c = sclr_a * sclr_b;
|
|
if (sclr_b != sclr_c / sclr_a)throw MyException("multiplicative inverse not inverse", __LINE__, __func__, SrcFilename);
|
|
if (false
|
|
|| !scalar::random().valid()
|
|
|| point::ptBase - point::ptBase != point::ptZero
|
|
|| scalar(1) * point::ptBase != point::ptBase
|
|
|| point::ptZero != ristretto_test_vector[0x00]
|
|
|| point::ptBase != ristretto_test_vector[0x01]
|
|
|| point::ptZero + point::ptBase != ristretto_test_vector[0x01]
|
|
|| point::ptBase + point::ptBase != ristretto_test_vector[0x02]
|
|
|| ristretto_test_vector[0x02] + point::ptBase != ristretto_test_vector[0x03]
|
|
|| ristretto_test_vector[0x02] + ristretto_test_vector[0x02] != ristretto_test_vector[0x04]
|
|
|| point::ptBase * 3 == ristretto_test_vector[0x04]
|
|
|| scalar(4).timesBase() != ristretto_test_vector[0x04]
|
|
|| ristretto_test_vector[0x03] + ristretto_test_vector[0x02] != ristretto_test_vector[0x05]
|
|
|| !(true
|
|
&& !point::ptZero
|
|
&& false == !point::ptBase
|
|
&& scalar(0).valid()==false
|
|
&& scalar(1).valid()
|
|
&& scalar::random().valid()
|
|
&& !ristretto_test_vector[0x00]
|
|
&& point::ptBase * 6 == ristretto_test_vector[0x06]
|
|
&& scalar(7) * point::ptBase == ristretto_test_vector[0x07]
|
|
&& point::ptBase * 3 + point::ptBase * 5 == ristretto_test_vector[0x08]
|
|
&& (point::ptBase * scalar(3)) * 3 == ristretto_test_vector[0x09]
|
|
&& point::ptBase * (scalar(3) * 3) == ristretto_test_vector[0x09]
|
|
&& point::ptBase * 2 + point::ptBase * 3 == ristretto_test_vector[0x0A] - ristretto_test_vector[0x05]
|
|
&& !(ristretto_test_vector[0x02] == ristretto_test_vector[0x03])
|
|
&& (point::ptBase * 2 + ristretto_test_vector[0x03]) + point::ptBase * 4 == ristretto_test_vector[0x0B] - scalar(2) * point::ptBase
|
|
&& point::ptBase * 2 + ristretto_test_vector[0x03] + scalar(4) * point::ptBase == ristretto_test_vector[0x0B] - point::ptBase * 2
|
|
&& ristretto_test_vector[0x0C] - point::ptBase * 4 == point::ptBase * 4 + ristretto_test_vector[0x02] * 2
|
|
&& ristretto_test_vector[0x0D] + scalar(-1) * ristretto_test_vector[0x0B] == point::ptBase * 2
|
|
&& ristretto_test_vector[0x0E] == scalar(2) * scalar(7).timesBase()
|
|
&& ristretto_test_vector[0x0E] == scalar(2) * scalar(7) * point::ptBase
|
|
&& ristretto_test_vector[0x0F] == scalar(6) * scalar(5) * point::ptBase - scalar(0x0F).timesBase()
|
|
&& scalar(3) == scalar(2) + scalar(1)
|
|
&& scalar(3) == scalar(4) + scalar(-1)
|
|
&& scalar(0x0103) == scalar(0xFE) + scalar(5)
|
|
&& scalar(0xDEADBEEF) == scalar(0xDEADBDDE) + scalar(0x111)
|
|
&& scalar(0xBEEFDEADBEEF) == scalar(0xBEEFDEADBDDE) + scalar(0x111)
|
|
&& scalar(0xBEEF0000000F) + scalar(-0xBEEF0EADBEEF) == scalar(int(-0xEADBEE0))
|
|
)
|
|
)
|
|
{
|
|
errorCode = 9;
|
|
szError = "Fail\tCryptographic operations fail to generate expected test vectors.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
else
|
|
{
|
|
auto end_time{ std::chrono::high_resolution_clock::now() };
|
|
auto time_to_do_crypto{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
|
|
wxLogMessage(wxT("\t\ttest of ristretto test vectors took %lld microseconds"), time_to_do_crypto.count());
|
|
ILogMessage("\tTesting generation of shared secrets.");
|
|
start_time = std::chrono::high_resolution_clock::now();
|
|
scalar sclrAnonSessionSecretKey{ scalar::random() };
|
|
point ptAnonSessionPublicKey{ sclrAnonSessionSecretKey * point::ptBase };
|
|
|
|
scalar sclrServerPemanentSecretKey{ scalar::random() };
|
|
point ptServerPermanentPublicKey{ sclrServerPemanentSecretKey * point::ptBase };
|
|
const char* MessageTextToBeSecured = "hello";
|
|
hash<512> ServerCopyOfSharedSecret((sclrServerPemanentSecretKey * ptAnonSessionPublicKey), MessageTextToBeSecured);
|
|
hash<512> AnonCopyOfSharedSecret((sclrAnonSessionSecretKey * ptServerPermanentPublicKey), MessageTextToBeSecured);
|
|
// This method identifies Server to Anon, but because Anon's key is ephemeral, does not identify Anon to Server. For mutually identified encryption, Anon has to send a signature of the emphemeral public key by a non ephemeral public key inside the encrypted message, or a signature of the cleartext inside the encrypted message by a non ephemeral public key, depending on whether he wants to prove identity at all, prove identity to only Server, or to allow Server to prove it to anyone.
|
|
if (ServerCopyOfSharedSecret != AnonCopyOfSharedSecret) {
|
|
errorCode = 10;
|
|
szError = "Fail\tShared secret was not successfully shared.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
else {
|
|
end_time = std::chrono::high_resolution_clock::now();
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
|
|
wxLogMessage(_wx("\tshared secret generation both ways and comparison took %lld microseconds"), time_taken.count());
|
|
}
|
|
ILogMessage("\tTesting hashing speed");
|
|
{
|
|
int hashes{ 20000 };
|
|
point sharedSecretKey = sclrAnonSessionSecretKey * ptServerPermanentPublicKey;
|
|
ServerCopyOfSharedSecret = hash<512>(sharedSecretKey, MessageTextToBeSecured);
|
|
start_time = std::chrono::high_resolution_clock::now();
|
|
for (int i{ 0 }; i < hashes; i++) {
|
|
sharedSecretKey.blob[3] = ServerCopyOfSharedSecret.blob[5];
|
|
ServerCopyOfSharedSecret = hash<512>(sharedSecretKey, MessageTextToBeSecured);
|
|
}
|
|
end_time = std::chrono::high_resolution_clock::now();
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time) };
|
|
{ bool f = false; //dont optimize pointless calculation
|
|
for (auto x : ServerCopyOfSharedSecret.blob) { f = f || x; }
|
|
if (f) {
|
|
wxLogMessage(_wx("\t\t%d hashes of pairs of shared secrets took %lld microseconds"), hashes, time_taken.count());
|
|
wxLogMessage(_wx("\t\tin one millisecond, can hash %lld short messages"), 1000LL*hashes / time_taken.count());
|
|
}
|
|
}
|
|
}
|
|
ILogMessage("\tTesting hashing speed, hashes of hashes");
|
|
auto first{ "first" };
|
|
auto second{ "second" };
|
|
if ( hash(first, second)
|
|
!=
|
|
hash(
|
|
hsh() << first << second
|
|
)
|
|
) throw MyException("inconsistent hashes generated on strings", __LINE__, __func__, SrcFilename);
|
|
const char* _ = "hello";
|
|
hash a(_);
|
|
hash b(a);
|
|
hash c(b);
|
|
if (hash(b, c) != hash(hsh() << b << c)
|
|
) throw MyException("inconsistent hashes generated", __LINE__, __func__, SrcFilename);
|
|
constexpr int hashes{ 3000 };
|
|
start_time = std::chrono::high_resolution_clock::now();
|
|
for (int i{ 0 }; i < hashes; i++) {
|
|
a = hash(b, c);
|
|
b = hash(c, a);
|
|
c = hash(a, b);
|
|
}
|
|
end_time = std::chrono::high_resolution_clock::now();
|
|
auto time_taken{ std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time) };
|
|
{ bool f = false; //dont optimize pointless calculation away
|
|
for (auto x : c.blob) { f = f || x; }
|
|
if (f) {
|
|
wxLogMessage(_wx("\t\t%d hashes of a pair of hashes took %lld microseconds"), hashes * 3, time_taken.count());
|
|
if (time_taken.count() > 0) {
|
|
wxLogMessage(_wx("\t\tin one millisecond, can hash %lld pairs of 256 bit hashes"), hashes * 3000ll / time_taken.count());
|
|
}
|
|
}
|
|
}
|
|
{
|
|
std::unique_ptr<uint8_t[]> uBuff = std::make_unique<uint8_t[]>(hashes * 3 * 64);
|
|
auto sBuff = std::span<uint8_t>(uBuff.get(), hashes * 3 * 64);
|
|
randombytes_buf(sBuff);
|
|
start_time = std::chrono::high_resolution_clock::now();
|
|
c = hash(sBuff);
|
|
end_time = std::chrono::high_resolution_clock::now();
|
|
time_taken = std::chrono::duration_cast<std::chrono::microseconds> (end_time - start_time);
|
|
{ bool f = false; //dont optimize pointless calculation away
|
|
for (auto x : c.blob) { f = f || x; }
|
|
if (f) {
|
|
wxLogMessage(_wx("\t\tone hash of %d bytes took %lld microseconds"), hashes * 3 * 64, time_taken.count());
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception & e) {
|
|
errorCode = 10;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 10;
|
|
ILogError(szError.c_str());
|
|
}
|
|
unit_test_action = &TestSignatures;
|
|
return true;
|
|
}
|
|
} //end ristretto255 namespace
|
|
|
|
static void bigEndian(std::array<uint8_t, 8> & out, uint64_t in) {
|
|
for (int i = 7; i >= 0; i-- ) {
|
|
out[i] = in & 0xFF;
|
|
in = in >> 8;
|
|
}
|
|
}
|
|
|
|
static uint64_t shiftArbRight(uint64_t in, unsigned int i) {
|
|
if (i > 0) {
|
|
//shifting right any number of bits
|
|
if (i > 63) in = 0;
|
|
else in = (in >> i);
|
|
}
|
|
return in;
|
|
}
|
|
static uint64_t shiftArbLeft(uint64_t in, unsigned int i) {
|
|
//shifting left any number of bits
|
|
if (i > 0) {
|
|
if (i > 63)in = 0;
|
|
else in = (in << i);
|
|
}
|
|
return in;
|
|
}
|
|
|
|
//extern std::array<const uint8_t, 0x100>reverseLookup();
|
|
|
|
static bool slash6UnitTest(void) {
|
|
try {
|
|
static int count{ 0 };
|
|
if (count == 0) {
|
|
wxLogMessage(_wx(R"( decoding slash6 test vector to get expected result vector)"));
|
|
{
|
|
std::array<uint8_t, 56> a;
|
|
a[51] = a[52] = a[53] = a[54] = a[55] = 0xcc;
|
|
a[51] = 0xFF;
|
|
decltype(a) expectedResult{ 0x00, 0x00, 0x01, 0x04, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8b, 0x30, 0xd3, 0x8f, 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96, 0x9b, 0x71, 0xd7, 0x9f, 0x82, 0x18, 0xa3, 0x92, 0x59, 0xa7, 0xa2, 0x9a, 0xab, 0xb2, 0xdb, 0xaf, 0xc3, 0x1c, 0xb3, 0xd3, 0x5d, 0xb7, 0xe3, 0x9e, 0xbb, 0xf3, 0xdf, 0xbf,0xff,0xcc,0xcc,0xcc,0xcc };
|
|
// Trying it with guard bytes at the end.
|
|
if (base64_to_bits(&a[0], 0, 408, "0oOlI123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_") != 408
|
|
|| a != expectedResult) {
|
|
errorCode = 11;
|
|
szError = "Fail\tunexpected result of test vector.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
}
|
|
{
|
|
std::array<uint8_t, 51> a;
|
|
decltype(a) expectedResult{ 0x00, 0x00, 0x01, 0x04, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 0x8b, 0x30, 0xd3, 0x8f, 0x41, 0x14, 0x93, 0x51, 0x55, 0x97, 0x61, 0x96, 0x9b, 0x71, 0xd7, 0x9f, 0x82, 0x18, 0xa3, 0x92, 0x59, 0xa7, 0xa2, 0x9a, 0xab, 0xb2, 0xdb, 0xaf, 0xc3, 0x1c, 0xb3, 0xd3, 0x5d, 0xb7, 0xe3, 0x9e, 0xbb, 0xf3, 0xdf, 0xbf };
|
|
// Trying it without guard bytes to provoke address sanitizer.
|
|
if (base64_to_bits(&a[0], 0, 408, "0oOlI123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_") != 408
|
|
|| a != expectedResult) {
|
|
errorCode = 11;
|
|
szError = "Fail\tunexpected result of test vector.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
}
|
|
{
|
|
std::array<uint8_t, 10> buff;
|
|
randombytes_buf(buff);
|
|
std::array<char, 13>txt;
|
|
bits2base64(&buff[0], 0, 72, txt);
|
|
wxLogMessage(_wx("\trandom 72 bit integer: %s"), &txt[0]);
|
|
wxLogMessage(_wx("\tRandom text secret %s"), &DeriveTextSecret(ristretto255::scalar::random(), 1)[0]);
|
|
}
|
|
}
|
|
std::array<uint8_t, 8> buff;
|
|
randombytes_buf(buff);
|
|
std::array<char, 9 * 8 / 6 + 1>txt;
|
|
decltype(buff) buff2;
|
|
randombytes_buf(buff2);
|
|
randombytes_buf(txt);
|
|
decltype(buff2) buff3{ buff2 };
|
|
unsigned int length{ buff2[1] };
|
|
length = length % 60;
|
|
unsigned int start{ buff2[2] };
|
|
start = start % (64 - length);
|
|
unsigned int startDest{ buff2[3] };
|
|
startDest = startDest % (64 - length);
|
|
uint64_t sourceAs64bitInt{ 0 };
|
|
for (auto x : buff) sourceAs64bitInt = (sourceAs64bitInt << 8) | x;
|
|
if (count == 0)wxLogMessage(_wx("\tGenerating slash6 representation of %u random bits and converting back:"), length);
|
|
bits2base64(&buff[0], start, length, txt);
|
|
uint64_t originalStateOfDestination{ 0 };
|
|
for (auto x : buff2) originalStateOfDestination = (originalStateOfDestination << 8) | x;
|
|
/*wxLogDebug("start=%u, length=%u", start, length);
|
|
wxLogDebug("%02x%02x%02x%02x%02x%02x%02x%02x", buff[0], buff[1], buff[2], buff[3], buff[4], buff[5], buff[6], buff[7]);
|
|
wxLogDebug("%02x%02x%02x%02x%02x%02x%02x%02x", buff2[0], buff2[1], buff2[2], buff2[3], buff2[4], buff2[5], buff2[6], buff2[7]);*/
|
|
unsigned int newLength{ base64_to_bits(&buff2[0], startDest, length, &txt[0]) };
|
|
if (newLength != length) {
|
|
errorCode = 11;
|
|
szError = "Fail\tBitBuffer length discrepancy.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
/*wxLogDebug("startDest=%u", startDest);
|
|
wxLogDebug("%02x%02x%02x%02x%02x%02x%02x%02x", buff2[0], buff2[1], buff2[2], buff2[3], buff2[4], buff2[5], buff2[6], buff2[7]);*/
|
|
uint64_t finalStateOfDestination{ 0 };
|
|
for (auto x : buff2) finalStateOfDestination = (finalStateOfDestination << 8) | x;
|
|
uint64_t src{ shiftArbRight(shiftArbLeft(sourceAs64bitInt, start),(64 - length)) };
|
|
uint64_t dest{ shiftArbRight(shiftArbLeft(finalStateOfDestination, startDest),(64 - length)) };
|
|
uint64_t destOriginalLeadingBits{ shiftArbRight(originalStateOfDestination, 64 - startDest) };
|
|
uint64_t destFinalLeadingBits{ shiftArbRight(finalStateOfDestination, 64 - startDest) };
|
|
uint64_t destOriginalTrailingBits{ shiftArbLeft(originalStateOfDestination, (startDest + length)) };
|
|
uint64_t destFinalTrailingBits{ shiftArbLeft(finalStateOfDestination, (startDest + length)) };
|
|
|
|
/*wxLogDebug("check bits %d", int(src));*/
|
|
if (src != dest
|
|
|| destOriginalLeadingBits != destFinalLeadingBits
|
|
|| destOriginalTrailingBits != destFinalTrailingBits) {
|
|
wxLogMessage(_wx("start=%u, Destination start =%u, length=%u"), start, startDest, length);
|
|
wxLogMessage(_wx("%02x%02x%02x%02x%02x%02x%02x%02x"), buff[0], buff[1], buff[2], buff[3], buff[4], buff[5], buff[6], buff[7]);
|
|
wxLogMessage(_wx("%02x%02x%02x%02x%02x%02x%02x%02x"), buff3[0], buff3[1], buff2[3], buff3[3], buff3[4], buff3[5], buff3[6], buff3[7]);
|
|
wxLogMessage(_wx("%02x%02x%02x%02x%02x%02x%02x%02x"), buff2[0], buff2[1], buff2[2], buff2[3], buff2[4], buff2[5], buff2[6], buff2[7]);
|
|
if (src != dest) {
|
|
wxLogMessage(_wx("Bits changed in move, %x, %x"), (unsigned int)src, (unsigned int)dest);
|
|
}
|
|
if (destOriginalLeadingBits != destFinalLeadingBits) {
|
|
wxLogMessage(_wx("Bits preceding bit buffer changed, %x, %x"), (unsigned int)destOriginalLeadingBits, (unsigned int)destFinalLeadingBits);
|
|
}
|
|
if (destOriginalTrailingBits != destFinalTrailingBits) {
|
|
wxLogMessage(_wx("Bits following bit buffer changed, %x, %x"), (unsigned int)destOriginalTrailingBits, (unsigned int)destFinalTrailingBits);
|
|
}
|
|
errorCode = 12;
|
|
szError = "Fail\tBitBuffer discrepancy.";
|
|
ILogError(szError.c_str());
|
|
}
|
|
count++;
|
|
if (count > 4) {
|
|
wxLogMessage(_wx("\tSlash6 random bits test repeated %d times with different data, sizes, and positions"), count);
|
|
unit_test_action = &ristretto255::UnitTest;
|
|
}
|
|
// If you touch the slash6 encoding, try retesting with the number of tests tries set to one hundred thousand instead of five, to catch all code paths and weird special cases.
|
|
}
|
|
catch (const MyException& e) {
|
|
errorCode = e.what_num();
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (const std::exception & e) {
|
|
errorCode = 12;
|
|
szError = e.what();
|
|
ILogError(szError.c_str());
|
|
}
|
|
catch (...) {
|
|
szError = sz_unknown_error;
|
|
errorCode = 12;
|
|
ILogError(szError.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool StartUnitTest(void){
|
|
modal_dialog_hook.Register();
|
|
ILogMessage("Begin Unit Test for code compiled " __DATE__ " " __TIME__ ".");
|
|
// singletonFrame->m_pLogWindow->GetFrame()->Raise();
|
|
unit_test_action = &slash6UnitTest;
|
|
return true;
|
|
}
|
|
|
|
void UnitTest(wxIdleEvent & event) {
|
|
if((* unit_test_action)())event.RequestMore(true);
|
|
event.Skip();
|
|
}
|