d59729f396
fixed it by looking for funny things that deviated from the sameples, and doing various recommended safe things, and found a few sql errors, and one by one the crashes went away. The new wxWidgets just seems less tolerant of little careless stuff that is not right.
1192 lines
49 KiB
C++
1192 lines
49 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 locale. ☺");
|
|
try {
|
|
bool utfEnvironment{ true };
|
|
std::string cum{};
|
|
if constexpr (b_WINDOWS) {
|
|
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);
|
|
}
|
|
}
|
|
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();
|
|
}
|