Merge remote-tracking branch 'origin/master' into docs
This commit is contained in:
commit
776c18a4a6
@ -6,9 +6,10 @@
|
||||
whitespace = fix
|
||||
ignoreWhitespace = no
|
||||
[alias]
|
||||
lg = log --max-count=6 --oneline --pretty='format:%C(auto)%h %d %Creset%p %C("#60A0FF")%cr %Cgreen %cn %G? %GT trust%Creset%n%s%n'
|
||||
graph = log --max-count=18 --graph --pretty=format:'%C(auto)%h %s %Cgreen(%cr) %C(bold blue)%cn %G?%Creset' --abbrev-commit
|
||||
alias = ! git config --get-regexp ^alias\\. | sed -e s/^alias\\.// -e s/\\ /\\ =\\ / | grep -v ^'alias ' | sort
|
||||
lg = log --max-count=6 --pretty=format:'%C(auto)%h %d %Creset %p %C("#60A0FF")%cr %Cgreen %cn %G? %GT trust%Creset%n %<(78,trunc)%s%n'
|
||||
graph = log --graph --pretty=format:'%C(auto)%h %<(78,trunc)%s %Cgreen(%cr) %C(bold blue)%cn %G?%Creset' --abbrev-commit
|
||||
alias = !git config --get-regexp ^alias\\. | sed -e s/^alias\\.// -e s/\\ /\\ =\\ / | grep -v ^'alias ' | sort
|
||||
utcmt = !git commit --date=\"$(date --utc +%Y-%m-%dT%H:%M:%Sz)\"
|
||||
[commit]
|
||||
gpgSign = true
|
||||
[push]
|
||||
|
@ -1,5 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*">
|
||||
</assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<application>
|
||||
<windowsSettings>
|
||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
||||
|
@ -71,6 +71,7 @@
|
||||
<CompileAsManaged>false</CompileAsManaged>
|
||||
<CompileAsWinRT>false</CompileAsWinRT>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@ -79,13 +80,14 @@
|
||||
<AdditionalDependencies>mpir.lib;mpirxx.lib;libsodium.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
<Manifest>
|
||||
<AdditionalManifestFiles>wallet.manifest %(AdditionalManifestFiles)</AdditionalManifestFiles>
|
||||
<AdditionalManifestFiles>
|
||||
</AdditionalManifestFiles>
|
||||
</Manifest>
|
||||
<Manifest>
|
||||
<GenerateCategoryTags>false</GenerateCategoryTags>
|
||||
<GenerateCategoryTags>true</GenerateCategoryTags>
|
||||
</Manifest>
|
||||
<Manifest>
|
||||
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness>
|
||||
<EnableDpiAwareness>false</EnableDpiAwareness>
|
||||
</Manifest>
|
||||
<ResourceCompile />
|
||||
<CustomBuildStep />
|
||||
@ -108,6 +110,7 @@
|
||||
<AdditionalOptions>/Zc:__cplusplus /utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<CompileAsManaged>false</CompileAsManaged>
|
||||
<CompileAsWinRT>false</CompileAsWinRT>
|
||||
<TreatWarningAsError>true</TreatWarningAsError>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
@ -121,6 +124,9 @@
|
||||
<CustomBuildStep />
|
||||
<CustomBuildStep />
|
||||
<CustomBuildStep />
|
||||
<Manifest>
|
||||
<GenerateCategoryTags>true</GenerateCategoryTags>
|
||||
</Manifest>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="../src/app.h" />
|
||||
@ -138,7 +144,8 @@
|
||||
<ClInclude Include="../src/slash6.h" />
|
||||
<ClInclude Include="../src/stdafx.h" />
|
||||
<ClInclude Include="../src/welcome_to_rhocoin.h" />
|
||||
<ClInclude Include="..\src\sqlite3.h" />
|
||||
<ClInclude Include="../src/serialization.h" />
|
||||
<ClInclude Include="../src/sqlite3.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="../src/app.cpp" />
|
||||
|
@ -33,7 +33,6 @@
|
||||
#define SQLITE_MAX_EXPR_DEPTH 0 //Setting the maximum expression parse-tree depth to zero disables all checking of the expression parse-tree depth, which simplifies the code resulting in faster execution, and helps the parse tree to use less memory.
|
||||
#define SQLITE_OMIT_DECLTYPE 1 // By omitting the (seldom-needed) ability to return the declared type of columns from the result set of query, prepared statements can be made to consume less memory.
|
||||
#define SQLITE_OMIT_DEPRECATED 1
|
||||
#define SQLITE_DQS 0 //Don't accept double quoted string literals.
|
||||
#define SQLITE_OMIT_PROGRESS_CALLBACK 1
|
||||
#define SQLITE_OMIT_SHARED_CACHE 1
|
||||
#define SQLITE_OMIT_UTF16 1
|
||||
|
19
src/ILog.cpp
19
src/ILog.cpp
@ -35,15 +35,24 @@ void queue_error_message(const char* psz) {
|
||||
void queue_fatal_error(const char* psz) {
|
||||
// Used where throwing immediately would be disastrous, as in a destructor or when constructing the main frame
|
||||
if (!errorCode)errorCode = 10;
|
||||
queue_error_message(psz);
|
||||
singletonFrame->Close();
|
||||
queue_error_message(psz); // Message continues to display,
|
||||
// even after App window that contains the frame has closed
|
||||
singletonFrame->Close(); // Generates close event, which is handled as usual after this returns
|
||||
// Main window closes after the close message is handled, but App still running, displaying the message
|
||||
// put up by queue_error_message. When that message is closed, App finally terminates.
|
||||
}
|
||||
|
||||
MyException::MyException(const char* sz, int i, const char* func__, const char* FILE__) noexcept :
|
||||
// usage
|
||||
// throw MyException("Expected wallet file not found", __LINE__, __func__, __FILE__);
|
||||
err_number(i) {
|
||||
char buff[20];
|
||||
snprintf(buff, 20, "%d", i);
|
||||
err = std::string(sz) + "\nline " + buff + ", function " + func__ + ", file " + FILE__;
|
||||
err = std::format(R"|({}
|
||||
line {}, function {}, file {})|", sz, i, func__, FILE__);
|
||||
}
|
||||
|
||||
MyException::MyException(MyException e, int i, const char* func, const char* file) noexcept :
|
||||
err(std::format(R"|({}
|
||||
line {}, function {}, file {})|", e.what(), i, func, file)), err_number(e.what_num()) {}
|
||||
MyException::MyException(std::exception e, int i, const char* func, const char* file) noexcept:
|
||||
err(std::format(R"|({}
|
||||
line {}, function {}, file {})|", e.what(), i, func, file)), err_number(i) {}
|
@ -19,11 +19,12 @@ private:
|
||||
public:
|
||||
virtual ~MyException() override = default;
|
||||
MyException() = delete;
|
||||
explicit MyException(const std::string &m) noexcept :err(m.c_str()),err_number(-1){}
|
||||
explicit MyException(const char* sz) noexcept :err(sz),err_number(-1) {}
|
||||
explicit MyException(const std::string& m) noexcept :err(m.c_str()), err_number(-1) {}
|
||||
explicit MyException(const char* sz, int i) noexcept :err(sz), err_number(i) {}
|
||||
explicit MyException(const char* sz, int i, const char*, const char*) noexcept;
|
||||
// usage throw MyException("error", __LINE__, __func__, __FILE__);
|
||||
explicit MyException(const std::string& m, int i, const char* func, const char* file) noexcept : MyException(m.c_str(), i, func, file) {}
|
||||
explicit MyException(int, sqlite3*) noexcept;
|
||||
virtual const char* what() const override {
|
||||
return err.c_str();
|
||||
@ -31,6 +32,9 @@ public:
|
||||
virtual const int what_num() const {
|
||||
return err_number;
|
||||
}
|
||||
explicit MyException(MyException e, std::string str)noexcept : err(e.what() + str), err_number(e.what_num()) {}
|
||||
explicit MyException(MyException e, int i, const char* func, const char* file) noexcept;
|
||||
explicit MyException(std::exception e, int i, const char* func, const char* file) noexcept;
|
||||
};
|
||||
|
||||
class FatalException : public MyException {
|
||||
|
@ -25,7 +25,7 @@ void sqlite3_init() {
|
||||
errorCode = 7;
|
||||
szError = "Fatal Error: Sqlite library did not init.";
|
||||
// Cannot log the error, because logging not set up yet, so logging itself causes an exception
|
||||
throw FatalException(szError.c_str());
|
||||
throw FatalException(szError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ namespace ro {
|
||||
sql(std::unique_ptr<Icompiled_sql>&& p) :std::unique_ptr<Icompiled_sql>(p.release()) { }
|
||||
~sql() = default;
|
||||
template <typename T>auto column(int i) const {
|
||||
if constexpr (ro::is_blob_field_type<T>::value) {
|
||||
if constexpr (ro::blob_type<T>) {
|
||||
auto st = (*this)->Isqlite3_column_blob(i);
|
||||
if (st.size_bytes() != sizeof(T)) throw BadDataException();
|
||||
static_assert (std::is_standard_layout<T>(), "not standard layout");
|
||||
@ -107,7 +107,7 @@ namespace ro {
|
||||
template < typename T,
|
||||
typename std::enable_if<!is_sqlite3_field_type<T>::value, int >::type dummy_arg = 0 >
|
||||
void bind(int i, const T& j) {
|
||||
static_assert(ro::is_serializable<T>::value, "Don't know how to store this type in a database");
|
||||
static_assert(ro::has_machine_independent_representation<T>, "Don't know how to store this type in a database");
|
||||
(*this)->Isqlite3_bind(i, ro::serialize(j));
|
||||
}
|
||||
typedef Icompiled_sql::sql_result result;
|
||||
|
@ -1,72 +1,93 @@
|
||||
#include "stdafx.h"
|
||||
using ro::base58;
|
||||
static constexpr char SrcFilename[]{ "src/display_wallet.cpp" };
|
||||
display_wallet::display_wallet(wxWindow* parent, wxFileName& walletfile) :
|
||||
wxPanel(parent, myID_WALLET_UI, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, wxT("Wallet")),
|
||||
m_db(nullptr),
|
||||
m_menuitem_add_name(this, &display_wallet::add_name_event_handler)
|
||||
{
|
||||
wxLogMessage(wxT("Loading %s"), walletfile.GetFullPath());
|
||||
if (!walletfile.IsOk() || !walletfile.HasName() || !walletfile.HasExt()) throw MyException("unexpected file name");
|
||||
if (!walletfile.FileExists())throw MyException(
|
||||
walletfile.GetFullPath().append(" does not exist.").ToUTF8()
|
||||
);
|
||||
m_db.reset(Sqlite3_open(walletfile.GetFullPath().ToUTF8()));
|
||||
sql_read_from_misc read_from_misc(m_db);
|
||||
if (!read_from_misc(1) || read_from_misc.value<int64_t>() != WALLET_FILE_IDENTIFIER)throw MyException(sz_unrecognizable_wallet_file_format);
|
||||
if (!read_from_misc(2) || read_from_misc.value<int64_t>() != WALLET_FILE_SCHEMA_VERSION_0_0 || !read_from_misc(4))throw MyException(sz_unrecognized_wallet_schema);
|
||||
read_from_misc.read(m_MasterSecret);
|
||||
if (!m_MasterSecret.valid()) throw MyException(sz_cold_wallets_not_yet_implemented);
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_lSizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_rSizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_lSizer,0, wxGROW, 4);
|
||||
sizer->Add(m_rSizer, 50, wxGROW, 4);
|
||||
SetSizer(sizer);
|
||||
ro::sql read_keys(m_db, R"|(SELECT * FROM "Keys";)|");
|
||||
sql_read_name read_name(m_db); //*It would be better to have a select statement goes through the name table, in name order. This is unit test code wrongly repurposed.
|
||||
/* ro::sql sql_read_names(
|
||||
m_db,
|
||||
R"|(SELECT Names.name, Keys.pubkey FROM Names INNER JOIN Keys ON Names.ROWID=Keys.id AND Keys.use=1 ORDER BY Names.name;)|"){} */
|
||||
// m_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);
|
||||
if (!read_name(id)) throw MyException(sz_no_corresponding_entry);
|
||||
const char* name = read_name.name();
|
||||
if (m_MasterSecret(name).timesBase() != *pubkey)throw MyException(std::string(sz_public_key_of) + name + sz_fails_to_correspond);
|
||||
m_lSizer->Add(
|
||||
new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
name,
|
||||
wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT|wxST_ELLIPSIZE_END
|
||||
),
|
||||
10,
|
||||
wxEXPAND | // make horizontally stretchable
|
||||
wxALL, // and make border all around
|
||||
2);
|
||||
m_rSizer->Add(
|
||||
new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
"#" + base58(*pubkey).operator std::string(),
|
||||
wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxST_ELLIPSIZE_END
|
||||
),
|
||||
10,
|
||||
wxEXPAND | // make horizontally stretchable
|
||||
wxALL, // and make border all around
|
||||
2);
|
||||
}
|
||||
Bind(wxEVT_CLOSE_WINDOW, &display_wallet::OnClose, this);
|
||||
this->SetSize(this->GetParent()->GetClientSize());
|
||||
singletonFrame->m_LastUsedSqlite.Assign(walletfile);
|
||||
try {
|
||||
if (!walletfile.IsOk() || !walletfile.HasName() || !walletfile.HasExt()) throw MyException("unexpected file name", __LINE__, __func__, SrcFilename);
|
||||
if (!walletfile.FileExists())throw MyException(
|
||||
walletfile.GetFullPath().append(" does not exist.").ToUTF8(),
|
||||
__LINE__, __func__, SrcFilename);
|
||||
m_db.reset(Sqlite3_open(walletfile.GetFullPath().ToUTF8()));
|
||||
m_read_from_misc.reset(new sql_read_from_misc(m_db));
|
||||
m_read_names_and_keys.reset(new ro::sql(m_db,
|
||||
R"|(SELECT "Names".name AS name, "Keys".pubkey AS pubkey FROM "Names" INNER JOIN "Keys" )|"
|
||||
R"|(ON "Names"."ROWID"="Keys".id AND "Keys".use=1 ORDER BY LOWER(name), name COLLATE BINARY;)|"));
|
||||
if (!(*m_read_from_misc)(1) || m_read_from_misc->value<int64_t>() != WALLET_FILE_IDENTIFIER)throw MyException(sz_unrecognizable_wallet_file_format);
|
||||
if (!(*m_read_from_misc)(2) || m_read_from_misc->value<int64_t>() != WALLET_FILE_SCHEMA_VERSION_0_0 || !(*m_read_from_misc)(4))throw MyException(sz_unrecognized_wallet_schema);
|
||||
m_MasterSecret= *(m_read_from_misc->value<ristretto255::scalar>());
|
||||
ILogMessage(std::format("\t\tmaster secret: #{}", ro::base58(m_MasterSecret).operator const char* ()).c_str());
|
||||
if(!m_MasterSecret.valid()) throw MyException(sz_cold_wallets_not_yet_implemented, __LINE__, __func__, SrcFilename);
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_lSizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_rSizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_lSizer, 0, wxGROW, 4);
|
||||
sizer->Add(m_rSizer, 50, wxGROW, 4);
|
||||
SetSizer(sizer);
|
||||
try {
|
||||
while (m_read_names_and_keys->step() == Icompiled_sql::ROW) {
|
||||
std::string name = m_read_names_and_keys->column<const char*>(0);
|
||||
auto pubkey = *(m_read_names_and_keys->column<ristretto255::point>(1));
|
||||
if (m_MasterSecret(name).timesBase() != pubkey)throw MyException(std::string(sz_public_key_of) + name + sz_fails_to_correspond);
|
||||
m_lSizer->Add(
|
||||
new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
name,
|
||||
wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxST_ELLIPSIZE_END
|
||||
),
|
||||
10,
|
||||
wxEXPAND | // make horizontally stretchable
|
||||
wxALL, // and make border all around
|
||||
2);
|
||||
m_rSizer->Add(
|
||||
new wxStaticText(
|
||||
this,
|
||||
wxID_ANY,
|
||||
"#" + base58(pubkey).operator std::string(),
|
||||
wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT | wxST_ELLIPSIZE_END
|
||||
),
|
||||
10,
|
||||
wxEXPAND | // make horizontally stretchable
|
||||
wxALL, // and make border all around
|
||||
2);
|
||||
}
|
||||
}
|
||||
catch (const MyException& e) {
|
||||
throw MyException(e, __LINE__, __func__, SrcFilename);
|
||||
// sql exceptions tend to be unintelligible and excessively generic
|
||||
// because unclear what statement provoked them.
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
throw MyException(e, __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
catch (...) {
|
||||
szError = sz_unknown_error;
|
||||
throw MyException(sz_unknown_error, __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
Bind(wxEVT_CLOSE_WINDOW, &display_wallet::OnClose, this);
|
||||
this->SetSize(this->GetParent()->GetClientSize());
|
||||
singletonFrame->m_LastUsedSqlite.Assign(walletfile);
|
||||
|
||||
wxMenu* menuFile{ singletonFrame->GetMenuBar()->GetMenu(0) };
|
||||
singletonFrame->GetMenuBar()->EnableTop(1, true); //enable edit menu.
|
||||
wxMenu* menuEdit{ singletonFrame->GetMenuBar()->GetMenu(1) };
|
||||
m_menuitem_add_name.Insert(menuEdit, 0, "add name", "create new Zooko identity");
|
||||
wxMenu* menuFile{ singletonFrame->GetMenuBar()->GetMenu(0) };
|
||||
singletonFrame->GetMenuBar()->EnableTop(1, true); //enable edit menu.
|
||||
wxMenu* menuEdit{ singletonFrame->GetMenuBar()->GetMenu(1) };
|
||||
m_menuitem_add_name.Insert(menuEdit, 0, "add name", "create new Zooko identity");
|
||||
}
|
||||
catch (const MyException&) {
|
||||
throw;
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
throw MyException(e, __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
catch (...) {
|
||||
szError = sz_unknown_error;
|
||||
throw MyException(sz_unknown_error, __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ public:
|
||||
private:
|
||||
typedef MenuLink<display_wallet> MenuLink;
|
||||
std::unique_ptr<ISqlite3> m_db;
|
||||
std::unique_ptr <sql_read_from_misc> m_read_from_misc;
|
||||
std::unique_ptr <ro::sql> m_read_names_and_keys;
|
||||
ristretto255::CMasterSecret m_MasterSecret;
|
||||
wxBoxSizer* m_lSizer;
|
||||
wxBoxSizer* m_rSizer;
|
||||
|
@ -253,7 +253,7 @@ CREATE TABLE "Keys"(
|
||||
"ROWID" INTEGER PRIMARY KEY,
|
||||
"pubkey" BLOB NOT NULL UNIQUE,
|
||||
"id" integer NOT NULL,
|
||||
"use" 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);
|
||||
@ -261,14 +261,14 @@ 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" BLOB
|
||||
);
|
||||
"m" ANY
|
||||
) STRICT;
|
||||
COMMIT;)|");
|
||||
wxLogMessage("\t\tConstructing default wallet %s", filename.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.
|
||||
|
@ -57,7 +57,7 @@ namespace ro {
|
||||
bool is_alphanumeric_fixed_length(unsigned int, const char*);
|
||||
|
||||
template <class T> typename std::enable_if<
|
||||
ro::is_blob_field_type<T>::value,
|
||||
ro::blob_type<T>,
|
||||
decltype(T::type_indentifier, uint32_t())
|
||||
>::type fasthash(const T& p_blob) {
|
||||
static_assert(sizeof(T) % 8 == 0, "fasthash assumes a multiple of 8 bytes");
|
||||
@ -181,3 +181,4 @@ namespace ro {
|
||||
this->operator char* [this->length] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,345 +54,15 @@
|
||||
|
||||
void randombytes_buf(std::span<byte> in);
|
||||
void randombytes_buf(std::span<char > in);
|
||||
namespace ro {
|
||||
|
||||
// Decay to pointer is dangerously convenient,
|
||||
// but in some situations it is just convenient
|
||||
// This class provides an std:array one larger
|
||||
// than the compile time string size, which decays
|
||||
// to char*, std::string, and wxString
|
||||
// In some code, this is ambiguous, so casts
|
||||
// must sometimes be explicitly invoked.
|
||||
template <unsigned int stringlen>
|
||||
class CompileSizedString : public std::array<char, stringlen + 1>{
|
||||
public:
|
||||
static constexpr int length{ stringlen };
|
||||
CompileSizedString() {
|
||||
*(this->rbegin()) = '0';
|
||||
}
|
||||
CompileSizedString(char *psz) {
|
||||
auto tsz{ this->rbegin() };
|
||||
*tsz = '0';
|
||||
if (psz != nullptr) {
|
||||
auto usz = tsz + strlen;
|
||||
while (tsz < usz && *psz != '\0')
|
||||
*tsz++ = *psz++;
|
||||
*tsz = '\0';
|
||||
}
|
||||
}
|
||||
operator char* () & {
|
||||
char* pc = &(static_cast<std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
|
||||
operator const char* () const& {
|
||||
const char* pc = &(static_cast<const std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
operator const char* () const&& {
|
||||
const char* pc = &(static_cast<const std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
operator std::string() const& {
|
||||
return std::string((const char*)*this, this->length);
|
||||
}
|
||||
operator std::string() const&& {
|
||||
return std::string((const char*)*this, this->length);
|
||||
}
|
||||
operator wxString() const& {
|
||||
return wxString::FromUTF8Unchecked((const char*)(*this));
|
||||
}
|
||||
operator std::span<byte>() const& {
|
||||
return std::span<byte>(static_cast<std::nullptr_t>((char*)*this), stringlen + 1);
|
||||
}
|
||||
operator wxString() const&& {
|
||||
return wxString::FromUTF8Unchecked((const char*)(*this));
|
||||
}
|
||||
operator std::span<byte>() const&& {
|
||||
return std::span<byte>(static_cast<std::nullptr_t>((char*)*this), stringlen + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// This template generates a span over an indexable byte type,
|
||||
// such as a C array or an std::array, but not pointers
|
||||
template < typename T>
|
||||
std::enable_if_t<
|
||||
!std::is_pointer<T>::value &&
|
||||
sizeof(std::declval<T>()[0]) == 1,
|
||||
std::span<const byte>
|
||||
> serialize(const T& a) {
|
||||
return std::span<const byte>(static_cast<const byte *>(static_cast<std::nullptr_t>(&a[0])), std::size(a));
|
||||
}
|
||||
|
||||
// Compile time test to see if a type has a blob array member
|
||||
// This can be used in if constexpr (is_blob_field_type<T>::value)
|
||||
// By convention, blob fields are an std::array of unsigned bytes
|
||||
// therefore already serializable.
|
||||
template <class T> struct is_blob_field_type{
|
||||
template <typename U> static constexpr decltype(std::declval<U>().blob.size(), bool()) test() {
|
||||
return sizeof(std::declval<U>().blob[0])==1;
|
||||
}
|
||||
template <typename U> static constexpr bool test(int = 0) {
|
||||
return false;
|
||||
}
|
||||
static constexpr bool value = is_blob_field_type::template test<T>();
|
||||
};
|
||||
|
||||
// At present our serial classes consist of std::span<uint8_t> and custom classes that publicly inherit from std::span<byte>
|
||||
// To handle compound objects, add custom classes inheriting from std::span<byte>[n]
|
||||
|
||||
// template class that generates a std::span of bytes over the blob
|
||||
// field of any object containing a blob record, which is normally sufficient
|
||||
// for a machine independent representation of that object
|
||||
template <typename T>
|
||||
std::enable_if_t<
|
||||
is_blob_field_type<T>::value,
|
||||
std::span<const byte>
|
||||
> serialize(const T& pt) {
|
||||
return serialize(pt.blob);
|
||||
}
|
||||
|
||||
// method that assumes that any char * pointer points a null terminated string
|
||||
// and generates a std::span of bytes over the string including the terminating
|
||||
// null.
|
||||
// we assume the string is already machine independent, which is to say, we assume
|
||||
// it comes from a utf8 locale.
|
||||
|
||||
inline auto serialize(const char* sp) { return std::span(static_cast<char*>(static_cast<std::nullptr_t>(sp)), strlen(sp) + 1); }
|
||||
|
||||
inline auto serialize(const decltype(std::declval<wxString>().ToUTF8()) sz){
|
||||
return serialize(static_cast<const char*>(sz));
|
||||
}
|
||||
/*
|
||||
inline auto serialize(const wxString& wxstr) {
|
||||
return serialize(static_cast<const char*>(wxstr.ToUTF8()));
|
||||
}
|
||||
If we allowed wxwidgets string to be serializable, all sorts of surprising things
|
||||
would be serializable in surprising ways, because wxWidgets can convert all
|
||||
sorts of things into strings that you were likely not expecting, in ways
|
||||
unlikely to be machine independent, so you if you give an object to be
|
||||
hashed that you have not provided some correct means for serializing, C++ is
|
||||
apt to unhelpfully and unexpectedly turn it into a wxString,
|
||||
|
||||
If you make wxStrings hashable, suprising things become hashable.
|
||||
*/
|
||||
|
||||
// data structure containing a serialized signed integer.
|
||||
template<class T, std::enable_if_t<is_standard_unsigned_integer<T>, int> = 0>
|
||||
class userial : public std::span<byte> {
|
||||
public:
|
||||
std::array<byte, (std::numeric_limits<T>::digits + 6) / 7> bblob;
|
||||
userial(T i) {
|
||||
byte* p = &bblob[0] + sizeof(bblob);
|
||||
*(--p) = i & 0x7f;
|
||||
i >>= 7;
|
||||
while (i != 0) {
|
||||
*(--p) = (i & 0x7f) | 0x80;
|
||||
i >>= 7;
|
||||
}
|
||||
assert(p >= &bblob[0]);
|
||||
*static_cast<std::span<byte>*>(this) = std::span<byte>(p, &bblob[0] + sizeof(bblob));;
|
||||
}
|
||||
};
|
||||
|
||||
// data structure containing a serialized signed integer.
|
||||
template<class T, std::enable_if_t<is_standard_signed_integer<T>, int> = 0>
|
||||
class iserial : public std::span<byte> {
|
||||
public:
|
||||
std::array<byte, (std::numeric_limits<T>::digits + 7) / 7> bblob;
|
||||
iserial(T i) {
|
||||
// Throw away the repeated leading bits, and g
|
||||
byte* p = &bblob[0] + sizeof(bblob);
|
||||
unsigned count;
|
||||
if (i < 0) {
|
||||
size_t ui = i;
|
||||
count = (std::numeric_limits<size_t>::digits - std::countl_one(ui)) / 7;
|
||||
}
|
||||
else {
|
||||
size_t ui = i;
|
||||
count = (std::numeric_limits<size_t>::digits - std::countl_zero(ui)) / 7;
|
||||
}
|
||||
*(--p) = i & 0x7f;
|
||||
while (count-- != 0) {
|
||||
i >>= 7;
|
||||
*(--p) = (i & 0x7f) | 0x80;
|
||||
}
|
||||
assert(p >= &bblob[0]);
|
||||
*static_cast<std::span<byte>*>(this) = std::span<byte>(p, &bblob[0] + sizeof(bblob));;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// converts machine dependent representation of an integer
|
||||
// into a span pointin at a compact machine independent representation of an integer
|
||||
// The integer is split into seven bit nibbles in big endian order, with the high
|
||||
// order bit of the byte indicating that more bytes are to come.
|
||||
// for an unsigned integer, all high order bytes of the form 0x80 are left out.
|
||||
// for a positive signed integer, the same, except that the first byte
|
||||
// of what is left must have zero at bit 6
|
||||
// for a negative signed integer, all the 0xFF bytes are left out, except
|
||||
// that the first byte of what is left must have a one bit at bit six.
|
||||
//
|
||||
// small numbers get compressed.
|
||||
// primarily used by hash and hsh so that the same numbers on different
|
||||
// machines will generate the same hash
|
||||
template<typename T> std::enable_if_t<is_standard_unsigned_integer<T>, ro::userial<T> >
|
||||
serialize(T i) {
|
||||
return userial<T>(i);
|
||||
/* we don't need all deserialize functions to have the same name,
|
||||
indeed they have to be distinct because serialized data contains
|
||||
no type information, but for the sake of template code we need all
|
||||
things that may be serialized to be serialized by the serialize
|
||||
command, so that one template can deal with any
|
||||
number of serializable types */
|
||||
}
|
||||
template<typename T> std::enable_if_t<is_standard_signed_integer<T>, ro::iserial<T> >serialize(T i) {
|
||||
return iserial<T>(i);
|
||||
/* we don't need all deserialize functions to have the same name, but for the sake of template code we need all
|
||||
things that may be serialized to be serialized by the serialize command, so that one template can deal with any
|
||||
number of serializable types */
|
||||
}
|
||||
|
||||
// Turns a compact machine independent representation of an uninteger
|
||||
// into a 64 bit signed integer
|
||||
template<typename T> std::enable_if_t<is_standard_signed_integer<T>, T >
|
||||
deserialize(const byte* p) {
|
||||
auto oldp = p;
|
||||
T i;
|
||||
if (*p & 0x40)i = -64;
|
||||
else i = 0;
|
||||
while (*p & 0x80) {
|
||||
i = (i | (*p++ & 0x7F)) << 7;
|
||||
}
|
||||
if (p - oldp > (std::numeric_limits<int64_t>::digits + 6) / 7)throw BadDataException();
|
||||
return i | *p;
|
||||
}
|
||||
// Turns a compact machine independent representation of an integer
|
||||
// into a 64 bit unsigned integer
|
||||
template<typename T> std::enable_if_t<is_standard_unsigned_integer<T>, T >
|
||||
deserialize(const byte * p) {
|
||||
auto oldp = p;
|
||||
T i{ 0 };
|
||||
while (*p & 0x80) {
|
||||
i = (i | (*p++ & 0x7F)) << 7;
|
||||
}
|
||||
if (p - oldp > 9)throw BadDataException();
|
||||
return i | *p;
|
||||
}
|
||||
|
||||
// Turns a compact machine independent representation of an integer
|
||||
// into a 64 bit signed integer
|
||||
template<typename T> std::enable_if_t<is_standard_signed_integer<T> || is_standard_unsigned_integer<T>, T >
|
||||
deserialize(std::span<const byte> g) {
|
||||
byte* p = static_cast<std::nullptr_t>(&g[0]);
|
||||
T i{ deserialize<T>(p) };
|
||||
if (p > &g[0]+g.size())throw BadDataException();
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
It will be about a thousand years before numbers larger than 64 bits
|
||||
appear in valid well formed input, and bad data structures have to be
|
||||
dealt with a much higher level that knows what the numbers mean,
|
||||
and deals with them according to their meaning
|
||||
|
||||
Until then the low level code will arbitrarily truncate numbers larger
|
||||
than sixty four bits, but numbers larger than sixty four bits are
|
||||
permissible in input, are valid at the lowest level.
|
||||
|
||||
We return uint64_t, rather than uint_fast64_t to ensure that all
|
||||
implementations misinterpret garbage and malicious input in the
|
||||
same way.
|
||||
We cannot protect against Machiavelli perverting the input, so we
|
||||
don't try very hard to prevent Murphy perverting the input,
|
||||
but we do try to prevent Machiavelli from perverting the input in
|
||||
ways that will induce peers to disagree.
|
||||
|
||||
We use an explicit narrow_cast, rather than simply declaring th
|
||||
function to be uint64_t, in order to express the intent to uniformly
|
||||
force possibly garbage data being deserialized to standardized
|
||||
garbage.
|
||||
|
||||
We protect against malicious and ill formed data would cause the
|
||||
system to go off the rails at a point of the enemy's choosing,
|
||||
and we protect against malicious and ill formed data that one party
|
||||
might interpret in one way, and another party might interpret in a
|
||||
different way.
|
||||
|
||||
Ill formed data that just gets converted into well formed, but
|
||||
nonsense data can cause no harm that well formed nonsense data
|
||||
could not cause.
|
||||
|
||||
It suffices, therefore, to ensure that all implementations misinterpret
|
||||
input containing unreasonably large numbers as the same number.
|
||||
|
||||
Very large numbers are valid in themselves, but not going to be valid
|
||||
as part of valid data structures for a thousand years or so.
|
||||
|
||||
The largest numbers occurring in well formed valid data will be
|
||||
currency amounts, and the total number of the smallest unit of
|
||||
currency is fixed at 2^64-1 which will suffice for a thousand years.
|
||||
Or we might allow arbitrary precision floating point with powers of
|
||||
a thousand, so that sensible numbers to a human are represented by
|
||||
sensible numbers in the actuall representation.
|
||||
|
||||
secret keys, scalars are actually much larger numbers, modulo
|
||||
0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ecU
|
||||
but they are represented in a different format, their binary format
|
||||
being fixed size low endian format, as 256 bit numbers, though only
|
||||
253 bits are actually needed and used, and their human readable
|
||||
format being 44 digits in a base 58 representation.*/
|
||||
|
||||
// a compile time test to check if an object class has a machine independent representation
|
||||
template <typename T, typename... Args> struct is_serializable{
|
||||
template <typename U, typename... Args2>
|
||||
static constexpr decltype(ro::serialize(std::declval<U>()), bool()) test() {
|
||||
if constexpr (sizeof...(Args2) > 0) {
|
||||
return is_serializable::template test<Args2...>();
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
template <typename U, typename... Args2> static constexpr bool test(int = 0) {
|
||||
return false;
|
||||
}
|
||||
static constexpr bool value = is_serializable::template test<T,Args...>();
|
||||
};
|
||||
static_assert(ro::is_serializable<std::span<const char>>::value);
|
||||
static_assert(ro::is_serializable<std::span<const byte>>::value);
|
||||
|
||||
template<class T> ro::CompileSizedString< (2 * sizeof(T))>bin2hex(const T& pt) {
|
||||
ro::CompileSizedString< (2 * sizeof(T))>sz;
|
||||
sodium_bin2hex(&sz[0], sizeof(pt.blob) * 2 + 1, &pt.blob[0], pt.blob.size());
|
||||
return sz;
|
||||
}
|
||||
|
||||
template<class T> T hex2bin(const ro::CompileSizedString< (2 * sizeof(T))>& sz){
|
||||
T pt;
|
||||
size_t bin_len{ sizeof(T) };
|
||||
sodium_hex2bin(
|
||||
reinterpret_cast <unsigned char* const>(&pt),
|
||||
sizeof(T),
|
||||
&sz[0], 2 * sizeof(T),
|
||||
nullptr, &bin_len, nullptr
|
||||
);
|
||||
return pt;
|
||||
}
|
||||
|
||||
template <class T>decltype(std::declval<T>().blob, ro::CompileSizedString < (sizeof(T) * 8 + 5) / 6>()) to_base64_string(const T& p_blob) {
|
||||
ro::CompileSizedString < (sizeof(T) * 8 + 5) / 6> sz;
|
||||
bits2base64(
|
||||
&(p_blob.blob[0]), 0, sizeof(p_blob.blob) * 8,
|
||||
std::span<char>(sz)
|
||||
);
|
||||
return sz;
|
||||
}
|
||||
|
||||
} //End ro namespace
|
||||
|
||||
namespace ristretto255 {
|
||||
using ro::to_base64_string, ro::is_serializable;
|
||||
using
|
||||
ro::to_base64_string,
|
||||
ro::serialize, ro::bin2hex, ro::hex2bin,
|
||||
ro::bin2hex, ro::CompileSizedString,
|
||||
ro::has_machine_independent_representation;
|
||||
;
|
||||
// a class representing ristretto255 elliptic points
|
||||
class point;
|
||||
|
||||
@ -431,7 +101,9 @@ namespace ristretto255 {
|
||||
// It always a wise precaution to not use old type C arays, or wrap them
|
||||
// in a span.
|
||||
// Old type zero terminated strings work. The trailing zero is included
|
||||
// in the hash
|
||||
// in the hash, thus hash("the quick ", "brown fox") != hash("the quick brown fox")
|
||||
|
||||
|
||||
template<unsigned int hashsize = 256> class hsh {
|
||||
public:
|
||||
static_assert(hashsize > 63 && hashsize % 64 == 0 && crypto_generichash_BYTES_MIN * 8 <= hashsize && hashsize <= crypto_generichash_BYTES_MAX * 8, "Bad hash size.");
|
||||
@ -447,95 +119,35 @@ namespace ristretto255 {
|
||||
assert(i == 0);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ristretto255::hsh<hashsize>& operator << (const T& j) {
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte> >) {
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&(this->st),
|
||||
&j[0],
|
||||
j.size()
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return *this;
|
||||
}
|
||||
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&(this->st),
|
||||
(const unsigned char*)(j),
|
||||
strlen(j) + 1
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return *this;
|
||||
}
|
||||
else {
|
||||
auto sj = ro::serialize(j);
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&(this->st),
|
||||
(const unsigned char*)&sj[0],
|
||||
sj.size()
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
template<typename T, typename... Args,
|
||||
typename std::enable_if< is_serializable<const T, Args...>::value, int >::type dummy_arg = 0
|
||||
>explicit hsh(const T first, Args... args) {
|
||||
int i{ crypto_generichash_blake2b_init(
|
||||
&st,
|
||||
nullptr,0,
|
||||
hashsize / 8)
|
||||
};
|
||||
assert(i == 0);
|
||||
(*this) << first;
|
||||
if constexpr (sizeof...(args) > 0) {
|
||||
(*this).hashinto(args...);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename... Args,
|
||||
typename std::enable_if< ro::is_serializable<const T>::value, int >::type dummy_arg = 0
|
||||
> void hashinto(const T first, Args... args) {
|
||||
*this << first;
|
||||
if constexpr (sizeof...(args) > 0) {
|
||||
(*this).hashinto(args...);
|
||||
}
|
||||
}
|
||||
};
|
||||
/*
|
||||
template<unsigned int hashsize, typename T>
|
||||
ristretto255::hsh<hashsize>& operator <<(ristretto255::hsh<hashsize> &u, const T& j) {
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte> >) {
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&u.st,
|
||||
&j[0],
|
||||
j.size()
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return u;
|
||||
}
|
||||
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&u.st,
|
||||
(const unsigned char *)(j),
|
||||
strlen(j) + 1
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return u;
|
||||
}
|
||||
else {
|
||||
template<has_machine_independent_representation T, typename... Args>
|
||||
hsh<hashsize>& hashinto(const T& j, Args... args) {
|
||||
auto sj = ro::serialize(j);
|
||||
int i = crypto_generichash_blake2b_update(
|
||||
&u.st,
|
||||
&(this->st),
|
||||
(const unsigned char*)&sj[0],
|
||||
sj.size()
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return u;
|
||||
if constexpr (sizeof...(args) > 0) {
|
||||
(*this).hashinto(args...);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
template<has_machine_independent_representation T> hsh<hashsize>& operator << (const T& j) {
|
||||
auto sj = ro::serialize(j);
|
||||
auto i = crypto_generichash_blake2b_update(
|
||||
&(this->st),
|
||||
(const unsigned char*)&sj[0],
|
||||
sj.size()
|
||||
);
|
||||
if (i) throw HashReuseException();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
static_assert(!has_machine_independent_representation<hsh<256> >, "Don't want to partially hash partial hashes");
|
||||
|
||||
// This constructs a finalized hash.
|
||||
// If it has one argument, and that argument is hsh (unfinalized hash) object,
|
||||
@ -582,14 +194,13 @@ namespace ristretto255 {
|
||||
assert(i == 0);
|
||||
if (i) throw HashReuseException();
|
||||
}
|
||||
static_assert(!ro::is_serializable<hsh<hashsize> >::value, "Don't want to partially hash partial hashes");
|
||||
template<typename T, typename... Args,
|
||||
typename std::enable_if< ro::is_serializable<const T, Args...>::value, int >::type dummy_arg = 0
|
||||
>explicit hash(const T& first, Args... args) {
|
||||
template< has_machine_independent_representation T, typename... Args>explicit hash(const T& first, Args... args) {
|
||||
// not restraining the variant args by concept, because they get caught deeper in,
|
||||
// and visual studio reporting of variant concept errors sucks.
|
||||
hsh<hashsize> in;
|
||||
in << first;
|
||||
if constexpr (sizeof...(args) > 0) {
|
||||
in.hashinto(args...);
|
||||
in.hashinto( ro::trigger_error(args)...);
|
||||
}
|
||||
int i = crypto_generichash_blake2b_final(
|
||||
&in.st,
|
||||
@ -616,7 +227,7 @@ namespace ristretto255 {
|
||||
{
|
||||
// We will be reading points from the database, as blobs,
|
||||
// reading them from the network as blobs,
|
||||
// and reading them from human entered text as base52 encoded blobs.
|
||||
// and reading them from human entered text as base58 encoded blobs.
|
||||
// Therefore, invalid point initialization data is all too possible.
|
||||
public:
|
||||
static constexpr unsigned int type_indentifier = 1;
|
||||
@ -648,7 +259,7 @@ namespace ristretto255 {
|
||||
}
|
||||
point operator+(const point &pt) const& {
|
||||
point me;
|
||||
auto i{ crypto_core_ristretto255_add(&me.blob[0], &blob[0], &pt.blob[0]) };
|
||||
auto i{ crypto_core_ristretto255_add(_Out_ & me.blob[0], &blob[0], &pt.blob[0])};
|
||||
assert(i == 0);
|
||||
if (i != 0)throw NonRandomScalarException();
|
||||
return me;
|
||||
@ -656,7 +267,7 @@ namespace ristretto255 {
|
||||
|
||||
point operator-(const point& pt) const& {
|
||||
point me;
|
||||
auto i{ crypto_core_ristretto255_sub(&me.blob[0], &blob[0], &pt.blob[0]) };
|
||||
auto i{ crypto_core_ristretto255_sub(_Out_ &me.blob[0], &blob[0], &pt.blob[0]) };
|
||||
assert(i == 0);
|
||||
if (i != 0)throw NonRandomScalarException();
|
||||
return me;
|
||||
@ -671,6 +282,7 @@ namespace ristretto255 {
|
||||
"we need 512 bit randoms to ensure our points and scalars are uniformly distributed"
|
||||
);
|
||||
// There should be scalar from hash, not point from hash
|
||||
// libsodium already supplies a random point
|
||||
int i{
|
||||
crypto_core_ristretto255_from_hash(&blob[0], &(x.blob)[0]) };
|
||||
assert(i == 0);
|
||||
@ -678,7 +290,7 @@ namespace ristretto255 {
|
||||
|
||||
static point random(void) {
|
||||
point me;
|
||||
crypto_core_ristretto255_random(&(me.blob[0]));
|
||||
crypto_core_ristretto255_random(_Out_ &(me.blob[0]));
|
||||
return me;
|
||||
}
|
||||
|
||||
@ -731,14 +343,14 @@ namespace ristretto255 {
|
||||
// }
|
||||
scalar operator+(const scalar sclr) const& {
|
||||
scalar me;
|
||||
crypto_core_ristretto255_scalar_add(&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
crypto_core_ristretto255_scalar_add( _Out_&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
return me;
|
||||
}
|
||||
static_assert(sizeof(scalar::blob) == 32, "compiled");
|
||||
|
||||
scalar multiplicative_inverse() const &{
|
||||
scalar me;
|
||||
auto i = crypto_core_ristretto255_scalar_invert(&me.blob[0], &blob[0]);
|
||||
auto i = crypto_core_ristretto255_scalar_invert(_Out_&me.blob[0], &blob[0]);
|
||||
assert(i == 0);
|
||||
if (i != 0)throw NonRandomScalarException();
|
||||
return me;
|
||||
@ -746,13 +358,13 @@ namespace ristretto255 {
|
||||
|
||||
scalar operator-(const scalar& sclr) const& {
|
||||
scalar me;
|
||||
crypto_core_ristretto255_scalar_sub(&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
crypto_core_ristretto255_scalar_sub(_Out_&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
return me;
|
||||
}
|
||||
|
||||
scalar operator*(const scalar& sclr) const& {
|
||||
scalar me;
|
||||
crypto_core_ristretto255_scalar_mul(&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
crypto_core_ristretto255_scalar_mul(_Out_&me.blob[0], &blob[0], &sclr.blob[0]);
|
||||
return me;
|
||||
}
|
||||
|
||||
@ -766,7 +378,7 @@ namespace ristretto255 {
|
||||
|
||||
point operator*(const point& pt) const& {
|
||||
point me;
|
||||
auto i{ crypto_scalarmult_ristretto255(&me.blob[0], &blob[0], &pt.blob[0]) };
|
||||
auto i{ crypto_scalarmult_ristretto255(_Out_&me.blob[0], &blob[0], &pt.blob[0]) };
|
||||
assert(i == 0);
|
||||
if (i != 0)throw NonRandomScalarException();
|
||||
return me;
|
||||
@ -774,7 +386,7 @@ namespace ristretto255 {
|
||||
|
||||
point timesBase() const& {
|
||||
point me;
|
||||
auto i{ crypto_scalarmult_ristretto255_base(&me.blob[0], &blob[0]) };
|
||||
auto i{ crypto_scalarmult_ristretto255_base(_Out_ & me.blob[0], &blob[0]) };
|
||||
assert(i == 0);
|
||||
if (i != 0)throw NonRandomScalarException();
|
||||
return me;
|
||||
@ -787,7 +399,7 @@ namespace ristretto255 {
|
||||
|
||||
static scalar random(void) {
|
||||
scalar me;
|
||||
crypto_core_ristretto255_scalar_random(&me.blob[0]);
|
||||
crypto_core_ristretto255_scalar_random(_Out_ & me.blob[0]);
|
||||
return me;
|
||||
}
|
||||
|
||||
@ -819,26 +431,27 @@ namespace ristretto255 {
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(ro::blob_type<scalar> && !ro::blob_type<int>);
|
||||
static_assert(ro::is_blob_field_type<scalar>::value);
|
||||
static_assert(ro::is_blob_field_type<scalar&>::value);
|
||||
static_assert(ro::is_blob_field_type<point>::value);
|
||||
static_assert(ro::is_blob_field_type<hash<256> >::value);
|
||||
static_assert(false == ro::is_blob_field_type<char*>::value);
|
||||
static_assert(ro::is_serializable<scalar&>::value);
|
||||
static_assert(ro::is_serializable<hash<512>&>::value);
|
||||
static_assert(ro::has_machine_independent_representation<scalar&>);
|
||||
static_assert(ro::has_machine_independent_representation<hash<512>&>);
|
||||
static_assert(ro::is_blob_field_type<int>::value == false);
|
||||
static_assert(ro::is_serializable<unsigned int>::value);
|
||||
static_assert(ro::is_serializable<char*>::value);
|
||||
static_assert(ro::is_serializable<uint8_t*>::value == false); //false because uint8_t * has no inband terminator
|
||||
static_assert(false == ro::is_serializable<wxString>::value && !ro::is_constructible_from_v<hash<256>, wxString>, "wxStrings are apt to convert anything to anything, with surprising and unexpected results");
|
||||
static_assert(ro::is_serializable<decltype(std::declval<wxString>().ToUTF8())>::value == true);
|
||||
static_assert(ro::has_machine_independent_representation<unsigned int>);
|
||||
static_assert(ro::has_machine_independent_representation<char*>);
|
||||
static_assert(ro::has_machine_independent_representation<uint8_t*> == false); //false because uint8_t * has no inband terminator
|
||||
static_assert(false == ro::has_machine_independent_representation<wxString> && !ro::is_constructible_from_v<hash<256>, wxString>, "wxStrings are apt to convert anything to anything, with surprising and unexpected results");
|
||||
static_assert(ro::has_machine_independent_representation<decltype(std::declval<wxString>().ToUTF8())> == true);
|
||||
static_assert(ro::is_constructible_from_all_of<scalar, int, hash<512>, std::array<uint8_t, crypto_core_ristretto255_BYTES>>);
|
||||
static_assert(ro::is_constructible_from_all_of<hash<256>, char*, short, unsigned short, hash<512>, point, scalar>, "want to be able to hash anything serializable");
|
||||
static_assert(ro::is_constructible_from_all_of<hash<256>, char*, short, unsigned short, hash<512>, point, scalar>, "want to be able to hash anything has_machine_independent_representation");
|
||||
static_assert(false == ro::is_constructible_from_any_of<int, scalar, point, hsh<512>, hash<256>>);
|
||||
static_assert(false == ro::is_constructible_from_any_of <scalar, wxString, hash<256>, byte*>, "do not want indiscriminate casts");
|
||||
static_assert(false == ro::is_constructible_from_any_of <point, wxString, hash<256>, byte*>, "do not want indiscriminate casts ");
|
||||
static_assert(false == ro::is_constructible_from_v<hash<256>, float>);
|
||||
static_assert(ro::is_serializable<float>::value == false);//Need to convert floats to
|
||||
static_assert(ro::has_machine_independent_representation<float> == false);//Need to convert floats to
|
||||
// their machine independent representation, possibly through idexp, frexp
|
||||
// and DBL_MANT_DIG
|
||||
static_assert(sizeof(decltype(ro::serialize(std::declval<scalar>())[0])) == 1);
|
||||
|
354
src/serialization.h
Normal file
354
src/serialization.h
Normal file
@ -0,0 +1,354 @@
|
||||
namespace ro {
|
||||
|
||||
// Decay to pointer is dangerously convenient,
|
||||
// but in some situations it is just convenient
|
||||
// This class provides an std:array one larger
|
||||
// than the compile time string size, which decays
|
||||
// to char*, std::string, and wxString
|
||||
// In some code, this is ambiguous, so casts
|
||||
// must sometimes be explicitly invoked.
|
||||
template <unsigned int stringlen>
|
||||
class CompileSizedString : public std::array<char, stringlen + 1>{
|
||||
public:
|
||||
static constexpr int length{ stringlen };
|
||||
CompileSizedString() {
|
||||
*(this->rbegin()) = '0';
|
||||
}
|
||||
CompileSizedString(char *psz) {
|
||||
auto tsz{ this->rbegin() };
|
||||
*tsz = '0';
|
||||
if (psz != nullptr) {
|
||||
auto usz = tsz + strlen;
|
||||
while (tsz < usz && *psz != '\0')
|
||||
*tsz++ = *psz++;
|
||||
*tsz = '\0';
|
||||
}
|
||||
}
|
||||
operator char* () & {
|
||||
char* pc = &(static_cast<std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
|
||||
operator const char* () const& {
|
||||
const char* pc = &(static_cast<const std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
operator const char* () const&& {
|
||||
const char* pc = &(static_cast<const std::array<char, stringlen + 1>*>(this)->operator[](0));
|
||||
return pc;
|
||||
}
|
||||
operator std::string() const& {
|
||||
return std::string((const char*)*this, this->length);
|
||||
}
|
||||
operator std::string() const&& {
|
||||
return std::string((const char*)*this, this->length);
|
||||
}
|
||||
operator wxString() const& {
|
||||
return wxString::FromUTF8Unchecked((const char*)(*this));
|
||||
}
|
||||
operator std::span<byte>() const& {
|
||||
return std::span<byte>(static_cast<std::nullptr_t>((char*)*this), stringlen + 1);
|
||||
}
|
||||
operator wxString() const&& {
|
||||
return wxString::FromUTF8Unchecked((const char*)(*this));
|
||||
}
|
||||
operator std::span<byte>() const&& {
|
||||
return std::span<byte>(static_cast<std::nullptr_t>((char*)*this), stringlen + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// This template generates a span over an indexable byte type,
|
||||
// such as a C array or an std::array, but not pointers
|
||||
template<class T>
|
||||
concept byte_spannable = requires (T a) {
|
||||
std::size(a);
|
||||
a[0];
|
||||
} && sizeof(std::declval<T>()[0]) == 1;
|
||||
|
||||
template<byte_spannable T>
|
||||
auto serialize(const T& a) {
|
||||
int l;
|
||||
const void* pt;
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::string>) {
|
||||
l = a.length() + 1;
|
||||
pt = a.c_str();
|
||||
}
|
||||
else {
|
||||
l = std::size(a);
|
||||
pt = &a[0];
|
||||
}
|
||||
return std::span(static_cast<const byte *>(pt), l);
|
||||
}
|
||||
|
||||
// Compile time test to see if a type has a blob array member
|
||||
// This can be used in if constexpr (is_blob_field_type<T>::value)
|
||||
// By convention, blob fields are an std::array of unsigned bytes
|
||||
// therefore already serializable.
|
||||
template <class T> struct is_blob_field_type{
|
||||
template <typename U> static constexpr decltype(std::declval<U>().blob.size(), bool()) test() {
|
||||
return sizeof(std::declval<U>().blob[0])==1;
|
||||
}
|
||||
template <typename U> static constexpr bool test(int = 0) {
|
||||
return false;
|
||||
}
|
||||
static constexpr bool value = is_blob_field_type::template test<T>();
|
||||
};
|
||||
|
||||
template<class T> concept blob_type = ro::is_blob_field_type<T>::value;
|
||||
|
||||
|
||||
// At present our serial classes consist of std::span<uint8_t> and custom classes that publicly inherit from std::span<byte>
|
||||
// To handle compound objects, add custom classes inheriting from std::span<byte>[n]
|
||||
|
||||
// template class that generates a std::span of bytes over the blob
|
||||
// field of any object containing a blob record, which is normally sufficient
|
||||
// for a machine independent representation of that object
|
||||
template <blob_type T> std::span<const byte> serialize(const T& pt) {
|
||||
return serialize(pt.blob);
|
||||
}
|
||||
|
||||
// method that assumes that any char * pointer points a null terminated string
|
||||
// and generates a std::span of bytes over the string including the terminating
|
||||
// null.
|
||||
// we assume the string is already machine independent, which is to say, we assume
|
||||
// it comes from a utf8 locale.
|
||||
|
||||
inline auto serialize(const char* sp) { return std::span(static_cast<char*>(static_cast<std::nullptr_t>(sp)), strlen(sp) + 1); }
|
||||
|
||||
inline auto serialize(const decltype(std::declval<wxString>().ToUTF8()) sz){
|
||||
return serialize(static_cast<const char*>(sz));
|
||||
}
|
||||
/* Don't do this. Disaster ensues,
|
||||
|
||||
inline auto serialize(const wxString& wxstr) {
|
||||
return serialize(static_cast<const char*>(wxstr.ToUTF8()));
|
||||
}
|
||||
If we allowed wxwidgets string to be serializable, all sorts of surprising things
|
||||
would be serializable in surprising ways, because wxWidgets can convert all
|
||||
sorts of things into strings that you were likely not expecting, in ways
|
||||
unlikely to be machine independent, so you if you give an object to be
|
||||
hashed that you have not provided some correct means for serializing, C++ is
|
||||
apt to unhelpfully and unexpectedly turn it into a wxString,
|
||||
|
||||
If you make wxStrings hashable, suprising things become hashable.
|
||||
However, we do make the strange data structure provided by wxString.ToUTF8() hashable,
|
||||
so that the wxString will not be implicitly hashable, but will be explicitly hashable.
|
||||
*/
|
||||
|
||||
// data structure containing a serialized unsigned integer
|
||||
// Converts an unsigned integer to VLQ format, and creates a bytespan pointing at it.
|
||||
// VLQ format, Variable Length Quantity (It is a standard used by LLVM and others)
|
||||
// On reflection, VLQ format is not convenient for the intended usage (merkle patricia trees
|
||||
// representing SQL indexes, and a better format is to compress leading zero or leading 0xFF bytes
|
||||
// with the length of the run being implied by a count of the bytes following the run)
|
||||
|
||||
template<std::unsigned_integral T> class userial : public std::span<byte> {
|
||||
public:
|
||||
std::array<byte, (std::numeric_limits<T>::digits + 6) / 7> bblob;
|
||||
userial(T i) {
|
||||
byte* p = &bblob[0] + sizeof(bblob);
|
||||
*(--p) = i & 0x7f;
|
||||
i >>= 7;
|
||||
while (i != 0) {
|
||||
*(--p) = (i & 0x7f) | 0x80;
|
||||
i >>= 7;
|
||||
}
|
||||
assert(p >= &bblob[0]);
|
||||
*static_cast<std::span<byte>*>(this) = std::span<byte>(p, &bblob[0] + sizeof(bblob));;
|
||||
}
|
||||
};
|
||||
|
||||
// data structure containing a serialized signed integer,
|
||||
// Converts an signed integer to VLQ format, and creates a bytespan pointing at it.
|
||||
// VLQ format, Variable Length Quantity (It is a standard used by LLVM and others)
|
||||
template<std::signed_integral T> class iserial : public std::span<byte> {
|
||||
public:
|
||||
std::array<byte, (std::numeric_limits<T>::digits + 7) / 7> bblob;
|
||||
iserial(T i) {
|
||||
// Throw away the repeated leading bits, and g
|
||||
byte* p = &bblob[0] + sizeof(bblob);
|
||||
unsigned count;
|
||||
if (i < 0) {
|
||||
size_t ui = i;
|
||||
count = (std::numeric_limits<size_t>::digits - std::countl_one(ui)) / 7;
|
||||
}
|
||||
else {
|
||||
size_t ui = i;
|
||||
count = (std::numeric_limits<size_t>::digits - std::countl_zero(ui)) / 7;
|
||||
}
|
||||
*(--p) = i & 0x7f;
|
||||
while (count-- != 0) {
|
||||
i >>= 7;
|
||||
*(--p) = (i & 0x7f) | 0x80;
|
||||
}
|
||||
assert(p >= &bblob[0]);
|
||||
*static_cast<std::span<byte>*>(this) = std::span<byte>(p, &bblob[0] + sizeof(bblob));;
|
||||
}
|
||||
};
|
||||
|
||||
// converts machine dependent representation of an integer
|
||||
// into a span pointin at a compact machine independent representation of an integer
|
||||
// The integer is split into seven bit nibbles in big endian order
|
||||
// (VLQ format), with the high
|
||||
// order bit of the byte indicating that more bytes are to come.
|
||||
// for an unsigned integer, all high order bytes of the form 0x80 are left out.
|
||||
// for a positive signed integer, the same, except that the first byte
|
||||
// of what is left must have zero at bit 6
|
||||
// for a negative signed integer, all the 0xFF bytes are left out, except
|
||||
// that the first byte of what is left must have a one bit at bit six.
|
||||
//
|
||||
// small numbers get compressed.
|
||||
// primarily used by hash and hsh so that the same numbers on different
|
||||
// machines will generate the same hash
|
||||
template<std::unsigned_integral T> userial<T> serialize(T i) {
|
||||
return userial<T>(i);
|
||||
/* we don't need all deserialize functions to have the same name,
|
||||
indeed they have to be distinct because serialized data contains
|
||||
no type information, but for the sake of template code we need all
|
||||
things that may be serialized to be serialized by the serialize
|
||||
command, so that one template can deal with any
|
||||
number of serializable types */
|
||||
}
|
||||
template<std::signed_integral T> iserial<T> serialize(T i) {
|
||||
return iserial<T>(i);
|
||||
/* we don't need all deserialize functions to have the same name, but for the sake of template code we need all
|
||||
things that may be serialized to be serialized by the serialize command, so that one template can deal with any
|
||||
number of serializable types */
|
||||
}
|
||||
|
||||
// Turns a compact machine independent representation of an uninteger
|
||||
// into a 64 bit signed integer
|
||||
template<std::signed_integral T> T deserialize(const byte* p) {
|
||||
auto oldp = p;
|
||||
T i;
|
||||
if (*p & 0x40)i = -64;
|
||||
else i = 0;
|
||||
while (*p & 0x80) {
|
||||
i = (i | (*p++ & 0x7F)) << 7;
|
||||
}
|
||||
if (p - oldp > (std::numeric_limits<int64_t>::digits + 6) / 7)throw BadDataException();
|
||||
return i | *p;
|
||||
}
|
||||
// Turns a compact machine independent representation of an integer
|
||||
// into a 64 bit unsigned integer
|
||||
template<std::unsigned_integral T> T deserialize(const byte * p) {
|
||||
auto oldp = p;
|
||||
T i{ 0 };
|
||||
while (*p & 0x80) {
|
||||
i = (i | (*p++ & 0x7F)) << 7;
|
||||
}
|
||||
if (p - oldp > 9)throw BadDataException();
|
||||
return i | *p;
|
||||
}
|
||||
|
||||
// Turns a compact machine independent representation of an integer
|
||||
// into a 64 bit signed integer
|
||||
template<std::integral T> T deserialize(std::span<const byte> g) {
|
||||
byte* p = static_cast<std::nullptr_t>(&g[0]);
|
||||
T i{ deserialize<T>(p) };
|
||||
if (p > &g[0]+g.size())throw BadDataException();
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
It will be about a thousand years before numbers larger than 64 bits
|
||||
appear in valid well formed input, and bad data structures have to be
|
||||
dealt with a much higher level that knows what the numbers mean,
|
||||
and deals with them according to their meaning
|
||||
|
||||
Until then the low level code will arbitrarily truncate numbers larger
|
||||
than sixty four bits, but numbers larger than sixty four bits are
|
||||
permissible in input, are valid at the lowest level.
|
||||
|
||||
We return uint64_t, rather than uint_fast64_t to ensure that all
|
||||
implementations misinterpret garbage and malicious input in the
|
||||
same way.
|
||||
We cannot protect against Machiavelli perverting the input, so we
|
||||
don't try very hard to prevent Murphy perverting the input,
|
||||
but we do try to prevent Machiavelli from perverting the input in
|
||||
ways that will induce peers to disagree.
|
||||
|
||||
We use an explicit narrow_cast, rather than simply declaring th
|
||||
function to be uint64_t, in order to express the intent to uniformly
|
||||
force possibly garbage data being deserialized to standardized
|
||||
garbage.
|
||||
|
||||
We protect against malicious and ill formed data would cause the
|
||||
system to go off the rails at a point of the enemy's choosing,
|
||||
and we protect against malicious and ill formed data that one party
|
||||
might interpret in one way, and another party might interpret in a
|
||||
different way.
|
||||
|
||||
Ill formed data that just gets converted into well formed, but
|
||||
nonsense data can cause no harm that well formed nonsense data
|
||||
could not cause.
|
||||
|
||||
It suffices, therefore, to ensure that all implementations misinterpret
|
||||
input containing unreasonably large numbers as the same number.
|
||||
|
||||
Very large numbers are valid in themselves, but not going to be valid
|
||||
as part of valid data structures for a thousand years or so.
|
||||
|
||||
The largest numbers occurring in well formed valid data will be
|
||||
currency amounts, and the total number of the smallest unit of
|
||||
currency is fixed at 2^64-1 which will suffice for a thousand years.
|
||||
Or we might allow arbitrary precision floating point with powers of
|
||||
a thousand, so that sensible numbers to a human are represented by
|
||||
sensible numbers in the actual representation.
|
||||
|
||||
secret keys, scalars are actually much larger numbers, modulo
|
||||
0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ecU
|
||||
but they are represented in a different format, their binary format
|
||||
being fixed size low endian format, as 256 bit numbers, though only
|
||||
253 bits are actually needed and used, and their human readable
|
||||
format being 44 digits in a base 58 representation.*/
|
||||
|
||||
// a compile time test to check if an object class has a machine independent representation
|
||||
template <typename T, typename... Args> static constexpr bool serializable() {
|
||||
if constexpr (requires(T a) {
|
||||
serialize(a);
|
||||
}) {
|
||||
if constexpr (sizeof...(Args) > 0) return serializable<Args...>();
|
||||
else return true;
|
||||
}
|
||||
else return false;
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
concept has_machine_independent_representation = serializable<Args...>();
|
||||
|
||||
template<has_machine_independent_representation T>
|
||||
T trigger_error(T x) { return x; };
|
||||
|
||||
static_assert( !has_machine_independent_representation<double>
|
||||
&& has_machine_independent_representation<std::span<const byte>, char*, std::span<const char>>,
|
||||
"concepts needed");
|
||||
|
||||
template<class T> ro::CompileSizedString< (2 * sizeof(T))>bin2hex(const T& pt) {
|
||||
ro::CompileSizedString< (2 * sizeof(T))>sz;
|
||||
sodium_bin2hex(&sz[0], sizeof(pt.blob) * 2 + 1, &pt.blob[0], pt.blob.size());
|
||||
return sz;
|
||||
}
|
||||
|
||||
template<class T> T hex2bin(const ro::CompileSizedString< (2 * sizeof(T))>& sz){
|
||||
T pt;
|
||||
size_t bin_len{ sizeof(T) };
|
||||
sodium_hex2bin(
|
||||
reinterpret_cast <unsigned char* const>(&pt),
|
||||
sizeof(T),
|
||||
&sz[0], 2 * sizeof(T),
|
||||
nullptr, &bin_len, nullptr
|
||||
);
|
||||
return pt;
|
||||
}
|
||||
|
||||
template <class T>decltype(std::declval<T>().blob, ro::CompileSizedString < (sizeof(T) * 8 + 5) / 6>()) to_base64_string(const T& p_blob) {
|
||||
ro::CompileSizedString < (sizeof(T) * 8 + 5) / 6> sz;
|
||||
bits2base64(
|
||||
&(p_blob.blob[0]), 0, sizeof(p_blob.blob) * 8,
|
||||
std::span<char>(sz)
|
||||
);
|
||||
return sz;
|
||||
}
|
||||
|
||||
} //End ro namespace
|
10
src/stdafx.h
10
src/stdafx.h
@ -71,7 +71,7 @@ static_assert(wxUSE_STD_CONTAINERS == 1, "wxWidgets api out of date");
|
||||
static_assert(wxUSE_STD_STRING_CONV_IN_WXSTRING == 1, "want wxString to conform to std::string");
|
||||
static_assert(wxUSE_SECRETSTORE >0, "need wxSecretStore to actually work");
|
||||
// The api may be there, but will always return false if wxUSE_SECRETSTORE is zero
|
||||
static_assert(wxUSE_STD_DEFAULT == 1 && wxUSE_STL==1);
|
||||
//static_assert(wxUSE_STD_DEFAULT == 1 && wxUSE_STL==1);
|
||||
std::span<uint8_t>& operator^=(std::span<byte>&, byte *);
|
||||
#ifndef wxHAS_IMAGES_IN_RESOURCES
|
||||
#include "rho.xpm" //Defines the icon AAArho on linux
|
||||
@ -88,10 +88,16 @@ inline wxString _wx(const char* sz) { return wxString::FromUTF8Unchecked(sz); }
|
||||
// it provides an array spaceship operator that solves the inconsistency
|
||||
// problem in a better way. Spaceship compares of arrays in my code are
|
||||
// no longer ill formed.
|
||||
static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 2 && wxRELEASE_NUMBER == 2 && wxSUBRELEASE_NUMBER == 1 && wxVERSION_STRING == wxT("wxWidgets 3.2.2.1"), "expecting wxWidgets 3.2.2.1");
|
||||
static_assert(wxMAJOR_VERSION == 3, "expecting wxWidgets wxWidgets 3.2.0");
|
||||
static_assert(wxMINOR_VERSION == 2, "expecting wxWidgets wxWidgets 3.2.0");
|
||||
static_assert(wxRELEASE_NUMBER == 0, "expecting wxWidgets wxWidgets 3.2.0");
|
||||
static_assert(wxSUBRELEASE_NUMBER == 1, "expecting wxWidgets wxWidgets 3.2.0");
|
||||
static_assert(wxVERSION_STRING == wxT("wxWidgets 3.2.0"), "expecting wxWidgets wxWidgets 3.2.0");
|
||||
// static_assert(wxMAJOR_VERSION == 3 && wxMINOR_VERSION == 2 && wxRELEASE_NUMBER == 0 && wxSUBRELEASE_NUMBER == 1 && wxVERSION_STRING == wxT("wxWidgets 3.2.0"), "expecting wxWidgets wxWidgets 3.2.0");
|
||||
#include "rotime.h"
|
||||
#include "slash6.h"
|
||||
#include "ISqlite3.h"
|
||||
#include "serialization.h"
|
||||
#include "ristretto255.h"
|
||||
#include "secrets.h"
|
||||
#include "mpir_and_base58.h"
|
||||
|
@ -25,9 +25,12 @@ void ascii2test();
|
||||
extern const uint8_t* const ascii2six;
|
||||
|
||||
namespace testbed {
|
||||
using ristretto255::hash, ristretto255::hsh, ristretto255::scalar,
|
||||
using /*ristretto255::hash, ristretto255::hsh, */ristretto255::scalar,
|
||||
ristretto255::point, ro::serialize, ro::bin2hex, ro::hex2bin,
|
||||
ro::bin2hex, ro::fasthash,ro::CompileSizedString ;
|
||||
ro::bin2hex, ro::fasthash, ro::CompileSizedString,
|
||||
ro::base58, ro::has_machine_independent_representation;
|
||||
static constexpr char SrcFilename[]{ "src/testbed.cpp" };
|
||||
|
||||
|
||||
/* experimental code called during unit test
|
||||
Anything here is a residue of forgotten experiments,
|
||||
@ -36,21 +39,29 @@ namespace testbed {
|
||||
This is a playground, where you can do stuff without worrying you might
|
||||
inadvertently break something that matters
|
||||
|
||||
No mechanism for input is available. You generally do not need it because you
|
||||
hard code the testing data, and detect errors with asserts, rather than exceptions
|
||||
but, of course, it can post a dialog using postmessage, then immediately return
|
||||
and the dialog can then call anything.
|
||||
Output goes to the unit test log.
|
||||
|
||||
Uncaught exceptions result in unit test failure, but not in an error
|
||||
message in the main program UI.
|
||||
No mechanism for input is available. You generally do not need it
|
||||
because you hard code the testing data, and detect errors with
|
||||
asserts, rather than exceptions but, of course, it can post a
|
||||
dialog using postmessage, and the dialog can then call anything.
|
||||
|
||||
If using a dialog, exceptions within the dialog will result in an error message in the
|
||||
main program UI, rather than in the unit test result, since the unit test
|
||||
is over before the dialog runs.
|
||||
Uncaught exceptions result in unit test failure, and an error message
|
||||
at the end of the unit test, but not in an error message in the
|
||||
main program UI.
|
||||
|
||||
If using postmessage, execution of the code waits for the dialog to
|
||||
return, data from the dialog can be used in the testbed code, and
|
||||
uncaught exceptions in the dialog will result unit test failure
|
||||
and be reported in the unit test log.
|
||||
|
||||
If using queumessage, the testbed code will complete while the dialog
|
||||
is waiting, data cannot be returned for use in the testbed code,
|
||||
and uncaught exceptions in the dialog queued will appear in the main UI.
|
||||
*/
|
||||
|
||||
void testbed() {
|
||||
// queue_error_message("hello world");
|
||||
ascii2test();
|
||||
// queue_error_message("hello world");
|
||||
// throw MyException("hello world exception", __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ static bool EndUnitTest() {
|
||||
unit_test_action = &noaction;
|
||||
modal_dialog_hook.Unregister();
|
||||
next_action = &unexpected_unit_test;
|
||||
static std::string intestbed("Testbed: ");
|
||||
std::string intestbed("Testbed: ");
|
||||
try {
|
||||
testbed::testbed();
|
||||
throw EndUnitTestOfExceptions();
|
||||
@ -71,7 +71,7 @@ static bool EndUnitTest() {
|
||||
catch (const EndUnitTestOfExceptions&) {}
|
||||
catch (const MyException& e) {
|
||||
errorCode = e.what_num();
|
||||
szError = e.what();
|
||||
szError = intestbed + e.what();
|
||||
ILogError(szError.c_str());
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
@ -238,12 +238,14 @@ static bool checkDataConversionsProduceExpected(void){
|
||||
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,str_pt_a,33, 66ull };
|
||||
if (base58(hash_b).operator std::string() !=
|
||||
"i22EVNPsKRjdxYTZrPPu9mx6vnrBjosFix5F4gn2mb2kF"
|
||||
){
|
||||
throw MyException("unexpected hash of transformations", __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
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();
|
||||
@ -264,34 +266,28 @@ static bool checkDataConversionsProduceExpected(void){
|
||||
}
|
||||
|
||||
static bool CheckForUtfEnvironment(void) {
|
||||
ILogMessage("\tChecking for UTF locale.");
|
||||
ILogMessage("\tChecking for UTF locale. ☺");
|
||||
try {
|
||||
bool utfEnvironment{ true };
|
||||
wxString utfError{ wxT("") };
|
||||
std::string cum{};
|
||||
if constexpr (b_WINDOWS) {
|
||||
auto ACP{ GetACP() };
|
||||
// Check that windows thinks this is UTF8
|
||||
utfEnvironment = utfEnvironment && (ACP == 65001);
|
||||
if (!utfEnvironment) {
|
||||
utfError += wxString::Format(wxT("current code page %d—should be 65001☹, "), ACP);
|
||||
cum += std::format("current code page {}, —should be 65001☹, ", ACP);
|
||||
}
|
||||
}
|
||||
auto FontEncoding{ wxLocale::GetSystemEncoding() };
|
||||
utfEnvironment = utfEnvironment && (false
|
||||
|| (FontEncoding == wxFONTENCODING_UTF8)
|
||||
|| (FontEncoding == wxFONTENCODING_UTF16BE)
|
||||
|| (FontEncoding == wxFONTENCODING_UTF16LE)
|
||||
|| (FontEncoding == wxFONTENCODING_UTF32BE)
|
||||
|| (FontEncoding == wxFONTENCODING_UTF32LE)
|
||||
|| (FontEncoding == wxFONTENCODING_SYSTEM)
|
||||
);
|
||||
// check that wxWidgets thinks this is UTF8
|
||||
utfEnvironment = utfEnvironment && (FontEncoding == wxFONTENCODING_UTF8);
|
||||
if (!utfEnvironment) {
|
||||
utfError = wxString::Format(wxT("%swxFontEncoding %d—should be %d☹"),
|
||||
utfError,
|
||||
FontEncoding,
|
||||
wxFONTENCODING_UTF8);
|
||||
wxLogMessage(wxT("%s"), utfError);
|
||||
cum += std::format("swxFontEncoding {}, —should be {} ☹",
|
||||
(int)FontEncoding,
|
||||
(int)wxFONTENCODING_UTF8
|
||||
);
|
||||
throw MyException(cum, __LINE__, __func__, SrcFilename);
|
||||
}
|
||||
if (!utfEnvironment) { throw MyException(utfError); }
|
||||
}
|
||||
catch (const MyException& e) {
|
||||
errorCode = e.what_num();
|
||||
@ -414,18 +410,19 @@ static bool OpenWallet(void) {
|
||||
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 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 0x%s"), name, (wxString)(bin2hex(*pubkey)));
|
||||
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 {
|
||||
|
Loading…
Reference in New Issue
Block a user