Merge remote-tracking branch 'origin/master' into docs

This commit is contained in:
reaction.la 2023-10-02 23:05:21 +10:00
commit 776c18a4a6
No known key found for this signature in database
GPG Key ID: 99914792148C8388
17 changed files with 599 additions and 568 deletions

View File

@ -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]

View File

@ -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>

View File

@ -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" />

View File

@ -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

View File

@ -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) {}

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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';
}
}

View File

@ -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
View 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

View File

@ -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"

View File

@ -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);
}
}

View File

@ -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 {