1
0
forked from cheng/wallet

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

This commit is contained in:
reaction.la 2022-03-26 21:16:07 +11:00
commit ab4f440350
No known key found for this signature in database
GPG Key ID: 99914792148C8388
21 changed files with 554 additions and 261 deletions

2
.gitmodules vendored
View File

@ -8,4 +8,4 @@
ignore = dirty
[submodule "mpir"]
path = mpir
url = git://github.com/BrianGladman/mpir.git
url = https://github.com/BrianGladman/mpir.git

View File

@ -1,13 +1,12 @@
#include "stdafx.h"
thread_local thread_local__* thl{ nullptr };
wxIMPLEMENT_APP(App);
App::App()
{
assert (singletonApp == nullptr);
singletonApp = this;
if (thl == nullptr)thl = new thread_local__();
}
App::~App()

2
app.h
View File

@ -47,7 +47,7 @@ static constexpr wxCmdLineEntryDesc g_cmdLineDesc[] =
"If the log is displayed, then does not exit on completion of unit test.",
wxCMD_LINE_VAL_NONE, wxCMD_LINE_SWITCH_NEGATABLE},
{ wxCMD_LINE_SWITCH, "f", "focus", "-f or --focus causes focus events to be logged for debugging purposes. "
"Usually used as -lf or -lft, as logging them without displaying them is useless.",
"implies log",
wxCMD_LINE_VAL_NONE, wxCMD_LINE_SWITCH_NEGATABLE},
{ wxCMD_LINE_PARAM, "", "", "mywallet.wallet",
wxCMD_LINE_VAL_NONE, /*wxCMD_LINE_PARAM_MULTIPLE|*/wxCMD_LINE_PARAM_OPTIONAL},

View File

@ -1,75 +0,0 @@
#pragma once
// We should template this to use __popcnt64 if available
// but that is premature optimization
inline uint64_t bitcount(uint64_t c) {
c = c - ((c >> 1) & 0x5555555555555555);
c = ((c >> 2) & 0x3333333333333333) +
(c & 0x3333333333333333);
c = ((c >> 4) + c) & 0x0F0F0F0F0F0F0F0F;
c = ((c >> 8) + c) & 0x00FF00FF00FF00FF;
c = ((c >> 16) + c) & 0x0000FFFF0000FFFF;
c = ((c >> 32) + c) & 0x00000000FFFFFFFF;
return c;
}
// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog "Bit Hacks"
// Find ⌊log2⌋
// We should template this to use lzcnt64, __builtin_clz or _BitScanReverse if available,
// but that is premature optimization.
inline auto rounded_log2(uint32_t v) {
// This algorithm extends to 64 bits, by adding a step, shrinks to sixteen bits by removing a step.
decltype(v) r{ 0 }, s;
// This redundant initialization and redundant |= of r can be eliminated,
// but eliminating it obfuscates the simplicity of the algorithm.
s = (v > 0xFFFF) << 4; v >>= s; r |= s;
s = (v > 0x00FF) << 3; v >>= s; r |= s;
s = (v > 0x000F) << 2; v >>= s; r |= s;
s = (v > 0x0003) << 1; v >>= s; r |= s;
r |= (v >> 1);
// result of ⌊log2(v)⌋ is in r
return r;
}
// For trailing bits, consider int __builtin_ctz (unsigned int x)
// http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear
// Count the consecutive trailing zero bits
inline auto trailing_zero_bits(uint64_t v) {
unsigned int c;
if (v & 0x3F) {
v = (v ^ (v - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest
for (c = 0; v; c++) {
v >>= 1;
}
}
else {
c = 1;
if ((v & 0xffffffff) == 0) {
v >>= 32;
c += 32;
}
if ((v & 0xffff) == 0) {
v >>= 16;
c += 16;
}
if ((v & 0xff) == 0){
v >>= 8;
c += 8;
}
if ((v & 0xf) == 0){
v >>= 4;
c += 4;
}
if ((v & 0x3) == 0) {
v >>= 2;
c += 2;
}
if ((v & 0x1) == 0) {
v >>= 1;
c += 1;
}
c -= v & 0x01;
}
return c;
}

View File

@ -53,16 +53,17 @@ namespace ro {
return retval;
}
}
else if constexpr (std::is_same< T, std::span<const byte>>::value) {
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte>>) {
return (*this)->Isqlite3_column_blob(i);
}
else if constexpr (std::is_same< T, const char*>::value) {
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
auto sz{ (*this)->Isqlite3_column_text(i) };
// if (!IsValidUtf8String(sz)) throw NonUtf8DataInDatabase();
return sz;
}
else {
static_assert(false, "Don't know how to read this datatype from database");
assert(false);
//, "Don't know how to read this datatype from database");
return null();
}
}
@ -83,14 +84,15 @@ namespace ro {
j = (*this)->Isqlite3_column_int(i);
}
}
else if constexpr (std::is_same< T, std::span<const byte>>::value) {
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, std::span<const byte>>) {
j = (*this)->Isqlite3_column_blob(i);
}
else if constexpr (std::is_same< T, const char*>::value) {
else if constexpr (std::is_same_v<std::remove_cvref_t<T>, const char*>) {
j = (*this)->Isqlite3_column_text(i);
}
else {
static_assert(false, "Don't know how to read this datatype from database");
assert(false);
//, "Don't know how to read this datatype from database");
}
return *this;
}

View File

@ -83,10 +83,12 @@ try {
m_pLogWindow->GetFrame()->SetName(sz_unit_test_log);
m_pLogWindow->GetFrame()->SetIcon(wxICON(AAArho));
if (singletonApp->m_unit_test) {
wxLogMessage(_T("Command line specified unit test with%s exit on completion of unit test."),
wxLogMessage(_T("Command line specified %s unit test with%s exit on completion of unit test."),
singletonApp->m_complete_unit_test?_T("complete"): singletonApp->m_quick_unit_test?_T("quick"):_T(""),
singletonApp->m_display ? _T("out") : _T(""));
wxLogMessage(_T("If an error occurs during unit test, the program will return a non zero "
"error number on exit."));
wxLogMessage(_T(""));
}
}else {
wxLog::EnableLogging(false);
@ -97,7 +99,10 @@ try {
wxEVT_IDLE,
&UnitTest
);
if (singletonApp->m_log_focus_events)wxLogMessage(_T("Logging focus events"));
if (singletonApp->m_log_focus_events) {
wxLogMessage(_T("Logging focus events"));
wxLogMessage(_T(""));
}
if (singletonApp->m_params.empty()) {
wxLogMessage(_T("No wallet specified. Attempting to open last used wallet"));
}else {
@ -111,22 +116,26 @@ try {
SetIcon(wxICON(AAArho)); //Does not appear to do anything. Maybe it does something in Unix.
//wxICON is a namestring on windows, and a symbol on Unix
Bind(wxEVT_CLOSE_WINDOW, &Frame::OnClose, this);
Bind(wxEVT_CLOSE_WINDOW, &Frame::OnClose, this);
wxMenu* menuFile = new wxMenu;
menuFile->Append(wxID_NEW, _T("&New wallet..."), _T("New wallet file From new secret"));
menuFile->Bind(wxEVT_MENU, &Frame::OnSaveNew, this, wxID_NEW);
menuFile->Append(wxID_REFRESH, _T("Existing secret..."), _T("New Wallet File From the secret of an old wallet"));
menuFile->Bind(wxEVT_MENU, &Frame::RecreateWalletFromExistingSecret, this, wxID_REFRESH);
menuFile->Append(wxID_OPEN, _T("&Open existing wallet file..."), _T(""));
menuFile->Bind(wxEVT_MENU, &Frame::OnFileOpen, this, wxID_OPEN);
menuFile->Append(wxID_DELETE, _T("&Delete Wallet File"), _T("Delete ") + m_LastUsedSqlite.GetFullPath());
menuFile->Bind(wxEVT_MENU, &Frame::OnDelete, this, wxID_DELETE);
menuFile->AppendSeparator();
menuFile->Append(myID_DELETECONFIG, _T("&Reset defaults"), _T("Delete config file"));
menuFile->Bind(wxEVT_MENU, &Frame::OnDeleteConfiguration, this, myID_DELETECONFIG);
menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT);
menuFile->Bind(wxEVT_MENU, &Frame::OnExit, this, wxID_EXIT);
menuFile->Append(wxID_NEW, menu_strings[0].tail[0][0], menu_strings[0].tail[0][1]);
menuFile->Bind(wxEVT_MENU, &Frame::OnSaveNew, this, wxID_NEW);
menuFile->Append(wxID_REFRESH, menu_strings[0].tail[1][0], menu_strings[0].tail[1][1]);
menuFile->Bind(wxEVT_MENU, &Frame::RecreateWalletFromExistingSecret, this, wxID_REFRESH);
menuFile->Append(wxID_OPEN, menu_strings[0].tail[2][0], menu_strings[0].tail[2][1]);
menuFile->Bind(wxEVT_MENU, &Frame::OnFileOpen, this, wxID_OPEN);
menuFile->Append(wxID_DELETE, menu_strings[0].tail[3][0], menu_strings[0].tail[3][1] + m_LastUsedSqlite.GetFullPath());
menuFile->Bind(wxEVT_MENU, &Frame::OnDelete, this, wxID_DELETE);
menuFile->AppendSeparator();
menuFile->Append(myID_DELETECONFIG, menu_strings[0].tail[4][0], menu_strings[0].tail[4][1] + m_LastUsedSqlite.GetFullPath());
menuFile->Bind(wxEVT_MENU, &Frame::OnDeleteConfiguration, this, myID_DELETECONFIG);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
menuHelp->Bind(wxEVT_MENU, &Frame::OnAbout, this, wxID_ABOUT);
@ -137,8 +146,9 @@ try {
menuHelp->Bind(wxEVT_MENU, &Frame::OnDeleteLastSubwindow, this, myID_DELETE_LAST_SUBWINDOW);
// menu_OnFirstUse.Append(menuHelp, this, _T("New wallet"), _T("add new wallet"));
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
menuBar->Append(menuFile, menu_strings[0].head);
menuBar->Append(new wxMenu, menu_strings[1].head);
menuBar->Append(menuHelp, menu_strings[2].head);
SetMenuBar(menuBar);
CreateStatusBar();
// child controls

View File

@ -1,8 +1,8 @@
#pragma once
template <typename handler_class>
// This class exists to record the information
// needed to unbind a menu action and delete the corresponding item from the menu.
// when the handler is destroyed.
// This class exists to record the needed to unbind a drop down menu action and delete
// the corresponding item from the drop down menu when the handler is destroyed.
// (Because the handler is a method of an object that is about to be destroyed.)
// Also avoids the need for manually creating a new windowid to link each additional bind
// to each menu item, thus avoids the likelihood of mismatching binds and menu entries.
class MenuLink {

View File

@ -14,27 +14,30 @@ namespace ro {
template <class T>
constexpr bool is_standard_unsigned_integer =
_Is_any_of_v<std::remove_cv_t<T>, unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long>;
_Is_any_of_v<std::remove_cvref_t<T>, unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long>;
template <class T>
constexpr bool is_standard_signed_integer =
_Is_any_of_v<std::remove_cv_t<T>, signed char, signed short, signed int, signed long, signed long long>;
_Is_any_of_v<std::remove_cvref_t<T>, signed char, signed short, signed int, signed long, signed long long>;
// A compile time test to see if desired casts work, and make sure that
// undesired casts do not work
template <class T, class U> struct has_constructor {
template <class T, class U> struct is_constructible_from {
template <typename V> static constexpr decltype(T(std::declval<V>()), bool()) test() {
return true;
}
template <typename V> static constexpr bool test(int = 0) {
return false;
}
static constexpr bool value = has_constructor::template test<U>();
static constexpr bool value = is_constructible_from::template test<U>();
};
template <class T, class U>
inline constexpr bool is_constructible_from_v = is_constructible_from<T, U>::value;
template <class T, class... _Types>
inline constexpr bool is_constructible_from_any_of = //true if T is constructible from any of the listed types
std::disjunction<has_constructor<T, _Types>...>::value;
std::disjunction<is_constructible_from<T, _Types>...>::value;
template <bool _First_value, class _First, class... _Rest>
struct _Junction { // handle false trait or last trait
@ -56,10 +59,99 @@ namespace ro {
template <class T, class... _Types>
inline constexpr bool is_constructible_from_all_of = //true if T is constructible from any of the listed types
junction<has_constructor<T, _Types>...>::value;
junction<is_constructible_from<T, _Types>...>::value;
template<class T, class U>
inline constexpr bool is_same = std::is_same_v<std::remove_cvref_t<T>, std::remove_cvref_t<U>>;
// A compile time test to see if spaceship operator already exists
template <class T, class U> struct has_spaceship {
template <typename V>
static constexpr
decltype(
std::declval<
std::remove_cvref_t<T>
>()
<=>
std::declval<
std::remove_cvref_t <V>
>(),
bool()
)test() {
return true;
}
template <typename V>
static constexpr bool
test(int = 0) {
return false;
}
static constexpr bool value =has_spaceship::template test<U>();
};
template<class T, class U>
inline constexpr bool has_spaceship_v = has_spaceship<T, U>::value;
}
std::span<byte>& operator^=(std::span<byte>& lhs, byte* rhs) {
for (auto& j : lhs) { j ^= *rhs++; }
return lhs;
/*spaceship operator for any pair of types that
* do not already have a spaceship operator
* can be arguments for std::span
* and whose members have the spaceship operator
* noexistent values of the shorter array are considered zero if of
* arithmetic type, otherwise, considered greater than zero */
template<class T, class U >
std::enable_if_t<!ro::has_spaceship_v<T,U>, decltype(std::span(declval<T>())[0] <=> std::span(declval<U>())[0]) >operator <=>(
const T& lh, const U& rh
){
typedef decltype(lh[0] <=> rh[0]) spaceship_return_type;
auto slh{ std::span(lh) };
auto srh{ std::span(rh) };
auto minsize{ std::min(slh.size(), srh.size()) };
spaceship_return_type retval{ spaceship_return_type::equivalent };
auto plh{ slh.begin() };
auto prh{ srh.begin() };
auto common_end{ plh + minsize };
while (
plh < common_end
&&
(
std::is_eq(retval = plh[0] <=> prh[0])
)
) {
plh++;
prh++;
}
if (std::is_eq(retval)) {
if (slh.size() > srh.size()) {
assert(prh == srh.end() && plh < slh.end());
if constexpr (std::is_arithmetic<std::remove_cvref_t<decltype(std::declval<T>()[0])> >::value) {
while (
plh < slh.end()
&& std::is_eq(retval = plh[0] <=> 0)
) {
plh++;
}
}
else {
retval = spaceship_return_type::greater;
}
}
if (slh.size() < srh.size()) {
assert(plh == slh.end() && prh < srh.end());
if constexpr (std::is_arithmetic<std::remove_cvref_t<decltype(std::declval<U>()[0])> >::value) {
while (
prh < srh.end()
&& std::is_eq(retval = prh[0] <=> 0)
) {
prh++;
}
}
else {
retval = spaceship_return_type::less;
}
}
}
return retval;
}

@ -1 +1 @@
Subproject commit d30251f03e646abd07b5399654f1f5dcea9a6b38
Subproject commit 32cba2b5e90c2b98b61e8cc4c8105c0a27725fb0

View File

@ -50,7 +50,7 @@
// code is intentionally written to break on such locales.
// No intent to support local date formats.
// All dates and times shall be YYYY-MM-DDtHH-mm-ss (international format)
// All dates and times shall be YYYY-MM-DD_HH:mm:ss (international format)
// Local date formats are always ambiguous. Date localization is dangerous
// in a program intended to support promises and contracts.
@ -69,6 +69,23 @@ However my experience is that all the terribly clever solutions for
and break every time they issue a new skew of the operating system.
I don't trust them, and are not interested in learning about them.*/
//Menu strings
static const char * file_menu_strings[][2]{
{"&New wallet...", "New wallet file From new secret"},
{"Existing secret...","New Wallet File From the secret of an old wallet"},
{"&Open existing wallet file...",""},
{"&Delete Wallet File","Delete"},
{"&Reset defaults","Delete config file"}};
const menu_string menu_strings_[]{
{"&File", file_menu_strings},
{"&Edit", nullptr},
{"&Help", nullptr} };
const menu_string* menu_strings{ menu_strings_ };
// Strings for the startup display, load existing wallet or create new
// wallet.
const char sz_NewWallet[] {R"|(New Wallet)|"};

View File

@ -16,6 +16,14 @@
// file in several languages, it will matter again. Since network is utf8
// the whole program has to be utf, even if localization not an issue.
//Menu strings
struct menu_string {
const char* head;
const char*(* tail)[2];
};
extern const menu_string* menu_strings;
// Strings for the startup display, load existing wallet or create new
// wallet.
// String array for the english passphrase

View File

@ -29,8 +29,8 @@ namespace ro {
//template <> class base58<scalar> : public CompileSizedString<44> {};
static_assert(sizeof(base58<point>) == 46, "base58 point strings unexpected size");
static constexpr char index2cryptoBase58[]{ "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" };
static constexpr char index2MpirBase58[]{ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" };
static constexpr char index2cryptoBase58[] { "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" };
static constexpr char index2MpirBase58[] { "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv" };
void map_base_from_mpir58(char* p) {
static const charmap map(index2MpirBase58, index2cryptoBase58);
@ -65,3 +65,28 @@ namespace ro {
else assert(*terminal_null == '\0');
}
}
namespace mpir {
mpz ristretto25519_curve_order("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", 16);
}
/*
thread_local std::unique_ptr<thread_local__> thl2{ std::make_unique<thread_local__>() }; //Destructor is not called
std::unique_ptr<thread_local__> thl2{ std::make_unique<thread_local__>() }; //Destructor is called
looks like cannot use smart pointers with thread_local
The destructor will not be called on thread_local variables
The machinery of classes just does not work with thread local variables.
A thread local variable can only be defined outside of the stack,
its destructor will probably not be called, and there are whimsical,
arbitrary, and incomprehensible restrictions on its constructor
*/
// The class thread_locall__, which holds all my thread_local global values
// has to be explicitly constructed in code with new
// and explicitly with delete destructed at program
// termination
// Because the compiler will not generate code
// for construction and destruction of thread_local
// values.
thread_local thread_local__* thl{ nullptr };

View File

@ -23,8 +23,6 @@ namespace mpir {
extern mpz ristretto25519_curve_order;
}
// This global thread local object is explicitly constructed
// on the heap in the code on need once the thread starts running
// and destroyed when the thread exits.
@ -44,7 +42,9 @@ public:
// These are used all over the place as temporaries.
mpir::mpz q, r, n;
};
extern thread_local thread_local__*thl; //Constructor in app.obj.
extern thread_local thread_local__* thl;
//extern thread_local thread_local__* thl; //Constructor in app.obj.
// Destructor does not seem to get called, hence using a pointer
// and destroying explicitly, rather than an std::unique_ptr
// needs testing to figure out what is going on
@ -74,7 +74,7 @@ namespace ro {
void map_base_to_mpir58(const char*, char*, size_t);
template <typename T> class base58 : public CompileSizedString<
((sizeof(T) * 8 + 4) * 4943ui64 + 28955ui64) / 28956ui64 - (std::is_same_v<T, scalar> ? 1 : 0)> {
((sizeof(T) * 8 + 4) * 4943ui64 + 28955ui64) / 28956ui64 - (std::is_same_v<std::remove_cvref_t<T>, scalar> ? 1 : 0)> {
public:
// The rational number 4943 / 28956 is minisculy larger than log(2)/log(58)
// hence rounding up the nearest integer guarantees it will always be big enough.
@ -108,7 +108,7 @@ namespace ro {
if (thl == nullptr)thl = new thread_local__();
mpz_ui_pow_ui(thl->n, 58, base58<T>::length);
if constexpr (std::is_same_v<T, scalar>) {
if constexpr (std::is_same_v<std::remove_cvref_t<T>, scalar>) {
mpz_fdiv_q(thl->q, thl->n, mpir::ristretto25519_curve_order);
}
else {
@ -130,7 +130,7 @@ namespace ro {
map_base_to_mpir58(p, ps, strsc.length);
if (p[base58::length] > ' ')throw OversizeBase58String();
if (mpz_set_str(thl->n, ps, 58))throw BadStringRepresentationOfCryptoIdException();
if constexpr (std::is_same_v<T, scalar>) {
if constexpr (std::is_same_v<std::remove_cvref_t<T>, scalar>) {
mpz_fdiv_qr(thl->q, thl->r, thl->n, mpir::ristretto25519_curve_order);
}
else {
@ -153,7 +153,7 @@ namespace ro {
) {
mpir_ui ck{ (static_cast<uint64_t>(fasthash(sc)) * static_cast<uint64_t>(check_range_m<T>)) >> 32 };
mpz_import(thl->n, sizeof(sc.blob), -1, 1, -1, 0, &sc.blob[0]);
if constexpr (std::is_same_v<T, scalar>) {
if constexpr (std::is_same_v<std::remove_cvref_t<T>, scalar>) {
mpz_addmul_ui(thl->n, mpir::ristretto25519_curve_order, ck);
}
else {

View File

@ -27,14 +27,14 @@ namespace ristretto255 {
bool scalar::constant_time_required{ true };
bool point::constant_time_required{ true };
scalar::scalar(int i) {
constexpr scalar::scalar(int64_t i) {
if (i >= 0) {
auto k{ unsigned int(i) };
auto k{ uint64_t(i) };
for (auto& j : blob) { j = k; k = k >> 8; }
}
else{
else {
std::array<uint8_t, crypto_core_ristretto255_BYTES> absdata;
auto k{ unsigned int(-i) };
auto k{ uint64_t(-i) };
for (auto& j : absdata) { j = k; k = k >> 8; }
crypto_core_ristretto255_scalar_negate(&blob[0], &absdata[0]);
}
@ -56,6 +56,37 @@ namespace ristretto255 {
point point::ptBase({
0xe2, 0xf2, 0xae, 0x0a, 0x6a, 0xbc, 0x4e, 0x71, 0xa8, 0x84, 0xa9, 0x61, 0xc5, 0x00, 0x51, 0x5f, 0x58, 0xe3, 0x0b, 0x6a, 0xa5, 0x82, 0xdd, 0x8d, 0xb6, 0xa6, 0x59, 0x45, 0xe0, 0x8d, 0x2d, 0x76});
static constexpr scalar curveOrder({ 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7,
0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 });
const scalar& scalar::scOrder{ curveOrder };
std::strong_ordering scalar::operator<=>(const scalar& sclr) const& noexcept {
static_assert(std::endian::native == std::endian::little);
// Instead of a static assert, we really should have an if constexpr()
// that defines reverse iterators for little endian order
// and defines forward iterators for big endian order.
// but we will fix that when the time comes.
// as it depends on how the libsodium library will implement
// scalars on big endian order.
std::strong_ordering retval{ std::strong_ordering::equal };
constexpr auto bg = sizeof(scalar::blob);
typedef const std::array<uint64_t, sizeof(scalar::blob)/sizeof(uint64_t) >as_uint64_t;
auto lh{ reinterpret_cast<as_uint64_t&>(this->blob)};
auto rh{ reinterpret_cast<as_uint64_t&>(sclr.blob) };
static_assert(sizeof(as_uint64_t) == sizeof(sclr.blob));
auto p{ lh.end() };
auto q{ rh.end() };
do
{
auto vp{ *--p };
auto vq{ *--q };
retval = vp <=> vq;
}
while (std::is_eq(retval) &&
bool(p > lh.begin()));
return retval;
}
signed_text::signed_text(
const scalar &k, // Signer's secret key
@ -126,6 +157,3 @@ template<> CompileSizedString < (sizeof(scalar) * 8 + 5) / 6>
return sz;
}
namespace mpir {
mpz ristretto25519_curve_order("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", 16);
}

View File

@ -105,11 +105,11 @@ namespace ro {
}
};
// This template generates a span over const unsigned bytes for
// any range over bytes, such as a C array or an std::array
// 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<
sizeof(std::size(std::declval<T>())) >= sizeof(int) &&
!std::is_pointer<T>::value &&
sizeof(std::declval<T>()[0]) == 1,
std::span<const byte>
> serialize(const T& a) {
@ -453,8 +453,8 @@ namespace ristretto255 {
}
template < typename T>
decltype(ro::serialize(std::declval<T>()), hsh<hashsize>())&
operator <<(const T& j) {
decltype(ro::serialize(std::declval<std::remove_cvref<T>::type >()), hsh<hashsize>())&
operator <<(const T & j) {
return *this << ro::serialize(j);
}
@ -468,7 +468,7 @@ namespace ristretto255 {
}
}
hsh<hashsize>& operator <<(const std::span<const byte>& j) {
hsh<hashsize>& operator <<(const std::span<const byte> j) {
int i = crypto_generichash_blake2b_update(
&st,
&j[0],
@ -509,7 +509,12 @@ namespace ristretto255 {
// The program should by running in the UTF8 locale, attempts to set that
// locale on startup. and tests for success in the unit test.
template<unsigned int hashsize = 256> class hash {
static_assert(hashsize > 63 && hashsize % 64 == 0 && crypto_generichash_BYTES_MIN * 8 <= hashsize && hashsize <= crypto_generichash_BYTES_MAX * 8, "Bad hash size.");
static_assert(
hashsize > 63 && hashsize % 64 == 0
&& crypto_generichash_BYTES_MIN * 8 <= hashsize
&& hashsize <= crypto_generichash_BYTES_MAX * 8,
"Bad hash size."
);
friend point;
friend scalar;
friend hsh<hashsize>;
@ -561,18 +566,29 @@ namespace ristretto255 {
// which are conveniently of prime order.
class point
{
// 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 slash6 encoded blobs. Therefore, invalid point initialization data is all too possible.
// 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.
// Therefore, invalid point initialization data is all too possible.
public:
static constexpr unsigned int type_indentifier = 1;
std::array<uint8_t, crypto_core_ristretto255_BYTES> blob;
static_assert(std::is_trivially_copyable<std::array<uint8_t, crypto_core_ristretto255_BYTES>>(), "does not support memcpy init");
static_assert(sizeof(blob) == 32, "watch for size and alignment bugs. Everyone should standarize on 256 bit public keys except for special needs");
static_assert(
std::is_trivially_copyable<std::array<uint8_t, crypto_core_ristretto255_BYTES>>(),
"does not support memcpy init"
);
static_assert(sizeof(blob) == 32,
"watch for size and alignment bugs. "
"Everyone should standarize on 256 bit public keys except for special needs"
);
static point ptZero;
static point ptBase;
explicit point() = default;
// After loading a point as a blog from the network, from the database, or from user data typed as text, have to check for validity.
// After loading a point as a blog from the network, from the database,
// or from user data typed as text, have to check for validity.
bool valid(void) const { return 0 != crypto_core_ristretto255_is_valid_point(&blob[0]); }
explicit point(std::array<uint8_t, crypto_core_ristretto255_BYTES>&& in) : blob{ std::forward<std::array<uint8_t, crypto_core_ristretto255_BYTES>>(in) } { };
explicit constexpr point(std::array<uint8_t, crypto_core_ristretto255_BYTES>&& in) :
blob{ std::forward<std::array<uint8_t, crypto_core_ristretto255_BYTES>>(in) } { };
static_assert(crypto_core_ristretto255_BYTES == 32, "256 bit points expected");
~point() = default;
point(point&&) = default; // Move constructor
@ -582,10 +598,6 @@ namespace ristretto255 {
bool operator==(const point& pt) const& {
return ConstantTimeEqualityTest(*this, pt);
}
bool operator!=(const point& pt) const &{
return !ConstantTimeEqualityTest(*this, pt);
}
point operator+(const point &pt) const& {
point me;
auto i{ crypto_core_ristretto255_add(&me.blob[0], &blob[0], &pt.blob[0]) };
@ -607,7 +619,9 @@ namespace ristretto255 {
point operator*(int) const& noexcept;
explicit point(const hash<512>& x) noexcept {
static_assert(crypto_core_ristretto255_HASHBYTES * 8 == 512, "we need 512 bit randoms to ensure our points and scalars are uniformly distributed");
static_assert(crypto_core_ristretto255_HASHBYTES * 8 == 512,
"we need 512 bit randoms to ensure our points and scalars are uniformly distributed"
);
// There should be scalar from hash, not point from hash
int i{
crypto_core_ristretto255_from_hash(&blob[0], &(x.blob)[0]) };
@ -628,11 +642,6 @@ namespace ristretto255 {
};
// a class representing ristretto255 scalars
// very large integers modulo the order of the
// ristretto255 elliptic curve, which is
// conveniently of prime order.
class scalar
{
friend point;
@ -644,18 +653,30 @@ namespace ristretto255 {
~scalar() = default;
explicit constexpr scalar(std::array<uint8_t, crypto_core_ristretto255_BYTES>&& in) : blob{ in } {};
explicit constexpr scalar(std::array<uint8_t, crypto_core_ristretto255_BYTES>* in) :blob(*in) {};
explicit scalar(int);
explicit constexpr scalar(int64_t);
explicit constexpr scalar(int i):scalar(int64_t(i)){}
explicit constexpr scalar(uint64_t k){ for (auto& j : blob) { j = k; k = k >> 8; } }
explicit constexpr scalar(unsigned int i) :scalar(uint64_t(i)) {}
scalar(scalar&&) = default; // Move constructor
scalar(const scalar&) = default; // Copy constructor
scalar& operator=(scalar&&) = default; // Move assignment.
scalar& operator=(const scalar&) = default; // Copy assignment.
/* Don't need constant time equality test
bool operator==(const scalar& sc) const& {
return ConstantTimeEqualityTest(*this, sc);
}
bool operator!=(const scalar& sc) const& {
return !ConstantTimeEqualityTest(*this, sc);
}
}*/
std::strong_ordering operator <=>(const scalar& sc) const& noexcept;
// strangely, contrary to documentation, compiler generates operator>
// but fails to generate operator==
bool operator==(const scalar& sc) const& = default;
/* {
return (*this <=> sc) == 0;
}*/
//bool operator!=(const scalar& sc) const& {
// return !ConstantTimeEqualityTest(*this, sc);
// }
scalar operator+(const scalar sclr) const& {
scalar me;
crypto_core_ristretto255_scalar_add(&me.blob[0], &blob[0], &sclr.blob[0]);
@ -687,7 +708,7 @@ namespace ristretto255 {
return operator*(sclr.multiplicative_inverse());
}
scalar operator*(int i) const& {
scalar operator*(int64_t i) const& {
return operator * (scalar(i));
}
@ -719,17 +740,24 @@ namespace ristretto255 {
}
bool valid(void) const& {
return _sodium_sc25519_is_canonical(&blob[0]) != 0 ||
sodium_is_zero(&blob[0], crypto_core_ed25519_SCALARBYTES);
int x = sodium_is_zero(&blob[0], crypto_core_ed25519_SCALARBYTES);
return x==0 && *this<scOrder;
// The libsodium library allows unreduced scalars, but I do not
// except, of course, for scOrder itself
}
bool operator !() const& {
// We don't need constant time equality test on scalars
// since they normally appear in signatures, or are
// checked against a hash or a point
/*bool operator !() const& {
return 0 != sodium_is_zero(&blob[0], sizeof(blob));
}
}*/
/* explicit operator wxString() const&;
explicit operator std::string() const&;*/
static bool constant_time_required;
static const scalar& scOrder;
private:
void reverse(std::array < uint8_t, crypto_core_ristretto255_SCALARBYTES>const& ac) {
auto p = blob.rbegin();
@ -750,14 +778,14 @@ namespace ristretto255 {
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::has_constructor<hash<256>, wxString>::value, "wxStrings are apt to convert anything to anything, with surprising and unexpected results");
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::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(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::has_constructor<hash<256>, float>::value);
static_assert(false == ro::is_constructible_from_v<hash<256>, float>);
static_assert(ro::is_serializable<float>::value == false);//Need to convert floats to
// their machine independent representation, possibly through idexp, frexp
// and DBL_MANT_DIG

View File

@ -3,7 +3,8 @@ namespace ro {
// need to define my own time types:
typedef time_t time_t; // is always time in seconds past the epoch modulo 2 ^ 64
// equivalent to regular old time_t
// except in environments where time_t is seconds past the epoch modulo 2 ^ 32
// except in environments where time_t is seconds past the epoch
// modulo 2 ^ 32
ro::time_t time();
// equivalent to regular old time(nullptr);
// except in environments where time_t is seconds past the epoch modulo 2 ^ 32
@ -21,24 +22,26 @@ namespace ro {
typedef std::chrono::duration<uint32_t, std::ratio<1, 1000> > msec;
// duration past startup time modulo 2^32 Rolls over every forty eight days, making durations longer than twenty four days of unclear sign
// duration past startup time modulo 2^32 Rolls over every forty eight days, making
// durations longer than twenty four days of unclear sign
msec msec_since_epoch(void);
// Obliterates chrono's elegant but pain in the ass distinction between time points and durations.
// Which type distinction in practice gets in the way off all sorts of things that just need to be done.
// In truth, it is a distinction that only matters, and is indeed only meaningful, with respect to shared global consensus time, universal coordinated time
// It is not meaningful for steady time or high precision time, which means that all sorts of stuff gets assigned to the wrong type, a duration when you need it to be a time point, or a time point when you need it to be a duration.
// A time point is just a duration relative to some globally agreed consensus time point, and if you don't have such a consensus, forget it. And when you are measuring millisecond times, you do not have such a consensus
// millisecond clocks are not expected to be synchronized between systems, nor to measure milliseconds exactly nor to be exactly the same time unit between systems, but they are expected to advance similarly on both systems for the life of any connection, making the distinction between time points and durations problematic.
// We expect to compare the change in time on one system, with the change in time on another system, in which case the time point class gets in the way.
/*
std::array<char, 12> msec::operator() const {
std::array<char, 12> x;
return x;
}*/
// Obliterates chrono's elegant but pain in the ass distinction between time points and
// durations. Which type distinction in practice gets in the way off all sorts of things
// that just need to be done.
// In truth, it is a distinction that only matters, and is indeed only meaningful, with
// respect to shared global consensus time, universal coordinated time. It is not
// meaningful for steady time or high precision time, which means that all sorts of stuff
// gets assigned to the wrong type, a duration when you need it to be a time point, or a
// time point when you need it to be a duration.
//
// A time point is just a duration relative to some globally agreed consensus time point,
// and if you don't have such a consensus, forget it. And when you are measuring
// millisecond times, you do not have such a consensus.
//
// millisecond clocks are not expected to be synchronized between systems, nor to
// measure milliseconds exactly nor to be exactly the same time unit between systems,
// but they are expected to advance similarly on both systems for the life of any
// connection, making the distinction between time points and durations problematic.
// We expect to compare the change in time on one system, with the change in time
// on another system, in which case the time point class gets in the way.
}

View File

@ -5,24 +5,36 @@
#include <string>
//#include <initializer_list> // for initializer_list
//#include <type_traits>
#include <memory> // for shared_ptr, unique_ptr
//#include <memory> // for shared_ptr, unique_ptr
#include <span>
#include "ILog.h"
#include "localization.h"
#include "slash6.h"
// This base64 uses the following characters as the numerals 0 to 63:
// Table to convert six bit to ascii
static constexpr uint8_t index2base64[]{ "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_" };
static constexpr uint8_t index2base64[]{
"0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz!$*+-_"
};
// Unlike regular baseINV, and incompatible with it. Intended to encode stuff small enough that it might be human typed and human transmitted, therefore maps 'o' and 'O' to '0', 'I' and 'l' to '1'
// Unlike regular baseINV, and incompatible with it. Intended to encode stuff small enough
// that it might be human typed and human transmitted, therefore maps 'o' and 'O' to '0', 'I' and
// 'l' to '1'
// uses six url safe additional characters !$*+-_ to bring it up to six bits
static_assert(index2base64[63] == '_', "surprise numeral at 63");
// Being intended for small bits of data, assumes no whitespace within an entity
// Encode and decode are called with a bit buffer, consisting of an unsigned byte pointer, a starting bit position relative to the pointer, and a bit count. We assume that there is room enough in the object pointed to to accommodate the bytes referenced by Bit Position+BitCount. We don't change bits outside the range.
// The bit buffer does not need to be aligned, nor does it need to be a multiple of six bits, eight bits, or twenty four bits.
// If the input bit buffer to be encoded to base sixty four is not a multiple of six bits, the last base sixty four numeral output will represent the bit buffer padded with trailing zeroes.
// If there is no room in the output span for all the base sixty four digits, the encode routine will return a number of bits less than the size of the input bit buffer, less than the bitcount it was given.
// Encode and decode are called with a bit buffer, consisting of an unsigned byte pointer, a
// starting bit position relative to the pointer, and a bit count. We assume that there is room
// enough in the object pointed to to accommodate the bytes referenced by
// Bit Position+BitCount. We don't change bits outside the range.
// The bit buffer does not need to be aligned, nor does it need to be a multiple of six bits,
// eight bits, or twenty four bits.
// If the input bit buffer to be encoded to base sixty four is not a multiple of six bits, the
// last base sixty four numeral output will represent the bit buffer padded with trailing zeroes.
// If there is no room in the output span for all the base sixty four digits, the encode routine
// will return a number of bits less than the size of the input bit buffer, less than the bitcount
// it was given.
// Table to convert ascii to six bit.
static constexpr std::array<constexpr uint8_t, 0x100> ascii2six_ar{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //control characters
@ -43,17 +55,33 @@ static constexpr std::array<constexpr uint8_t, 0x100> ascii2six_ar{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // utf8 multibyte characters, all stops.
};
// Table to convert ascii to six bit as a good old fashioned non owning naked pointer to const, whose storage is owned by a const static which exists until the program terminates.
// Table to convert ascii to six bit as a good old fashioned non owning naked pointer to const,
// whose storage is owned by a const static which exists until the program terminates.
static const uint8_t *ascii2six{ &ascii2six_ar[0] };
// Compile time execution is C++ is a pain, because expressions are apt to unpredictably lose their constexpr character for reasons that are far from clear.
// You really have to write compile time code in templates as a language, which is the totally obscure and hard to use language apt to generate remarkably voluminous error messages will little obvious connection to the actual problem, and surprising result that are very difficult to predict in advance or understand at all.
//In general, the better solution is to have a routine that is called once and only once at the beginning of the program, which initializes a bunch of static const values, if that solution is adequate, or to have a preproces routine written in python which generates the required C files and header files.
// Compile time execution is C++ is a pain, because expressions are apt to unpredictably lose
// their constexpr character for reasons that are far from clear.
// You really have to write compile time code in templates as a language, which is the totally
// obscure and hard to use language apt to generate remarkably voluminous error messages
// will little obvious connection to the actual problem, and surprising result that are ver
// difficult to predict in advance or understand at all.
// In general, the better solution is to have a routine that is called once and only once at the
// beginning of the program, which initializes a bunch of static const values, if that solution is
// adequate, or to have a preproces routine written in python which generates the required C
// files and header files.
// After this experiment in compile time code, I swear off it.
// Decode does not have an input span of encoded characters, but a char *, because it assumes the string is always terminated by an invalid character, such as the trailing null at the end of string or a space, or any character that is not one of our base sixty four numerals
// If the there are not enough input base sixty four numerals, it returns a size less than requested.
// if the requested bit buffer is not a multiple of six bits, and the last base 64 numeral had trailing ones that would not fit in the buffer, rather than the expected trailing zeroes, then it returns a size larger than the buffer, as if it changed stuff outside the buffer but does not actually change bits outside the buffer. This is likely an error, because obviously we want to decode something from base sixty four that was originally decoded using the same sized buffer.
// Decode does not have an input span of encoded characters, but a char *, because it assumes
// the string is always terminated by an invalid character, such as the trailing null at the end of
// string or a space, or any character that is not one of our base sixty four numerals
// If the there are not enough input base sixty four numerals, it returns a size less than
// requested.
// if the requested bit buffer is not a multiple of six bits, and the last base 64 numeral had
// trailing ones that would not fit in the buffer, rather than the expected trailing zeroes, then
// it returns a size larger than the buffer, as if it changed stuff outside the buffer but does not
// actually change bits outside the buffer. This is likely an error, because obviously we want
// to decode something from base sixty four that was originally decoded using the same sized
// buffer.
static const uint8_t INV{ UCHAR_MAX };
@ -64,11 +92,13 @@ static_assert(CHAR_BIT + 1 == sizeof(lowBitMask), "expecting eight bits per char
// Converts bit a bit buffer into base 64 numerals
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight, six, or twenty four.
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight,
// six, or twenty four.
// If insufficient room is provided for the base 64 value, throws exception.
void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int length, std::span<char>base64Numerals) {
assert(length <= 6 * base64Numerals.size() - 6);
// If you hit this assert you probably passed in a negative number, overflowing the string buffer
// If you hit this assert you probably passed in a negative number, overflowing the
// string buffer
// which would cause you to write all over memory.
// Expects(length <= 6 * base64Numerals.size()-6);
if (length > 6 * base64Numerals.size() - 6)throw FatalException(sz_text_buffer_overflow);
@ -84,7 +114,9 @@ void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int len
unsigned int bitsInAccumulator{ 8 - start };
assert(bitsInAccumulator < 9);
unsigned int count{ length - bitsInAccumulator }; //count is bits lifted out of the buffer.
// We need to keep track of count, because the last byte lifted out of the buffer may well have to be an incomplete byte, so when count gets down to below 8, we have to special case loading the bitAccumulator.
// We need to keep track of count, because the last byte lifted out of the buffer may well
// have to be an incomplete byte, so when count gets down to below 8, we have to
// special case loading the bitAccumulator.
char * Outputsz{ &(base64Numerals[0]) };
auto endBaseNumeralBuffer{ Outputsz + length / 6 };
for (; e < endBaseNumeralBuffer; e++) {
@ -98,7 +130,8 @@ void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int len
assert(count + bitsInAccumulator + 6 * (unsigned int)(e - Outputsz) == length);
}
else {
assert(count + bitsInAccumulator >= 6); // Should be enough bits in buffer for all numerals produced by this for loop.
assert(count + bitsInAccumulator >= 6); // Should be enough bits in buffer for
// all numerals produced by this for loop.
bitAccumulator = (((bitAccumulator << 8) | (*bitBuffer++)) >> (8 - count));
bitsInAccumulator += count;
count = 0;
@ -109,7 +142,9 @@ void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int len
*e = index2base64[(bitAccumulator >> (bitsInAccumulator - 6)) & 0x3F];
bitsInAccumulator -= 6;
}
// When we drop out of the for loop, we may have more than 0 bits left but less than six, in which case we then have to special case the last numeral by filling the bit accumulator with our zeropadding.
// When we drop out of the for loop, we may have more than 0 bits left but less than six, in
// which case we then have to special case the last numeral by filling the bit accumulato
// with our zeropadding.
if (count) {
bitAccumulator = (((bitAccumulator << 8) | (*bitBuffer++)) >> (8 - count));
bitsInAccumulator += count;
@ -135,7 +170,8 @@ void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int len
// This may produce the runtime error that there are too few numerals to fill the bit buffer.
// Stops at the first invalid numeral - such as a space.
// This is intended for quite short bit fields, not for transmitting megabytes of data over lines that are not eight bit safe.
// This is intended for quite short bit fields, not for transmitting megabytes of data over lines
// that are not eight bit safe.
// Returns the actual size of the fill.
// If the bit buffer is not a multiple of six, the last numerals excess bits need to be zero
// If they are not zero will truncate the excess bits and and throw BadDataException.
@ -151,7 +187,11 @@ unsigned int base64_to_bits(uint8_t * bitBuffer, unsigned int start, unsigned in
unsigned int numeral;
uint8_t overflowBits{ '\0' };
uint8_t * p{ bitBuffer };
for (const char * e = base64Numerals; ((numeral = ascii2six[static_cast<unsigned char>(*e)]), (numeral < INV)); e++) {
for (
const char * e = base64Numerals;
((numeral = ascii2six[static_cast<unsigned char>(*e)]), (numeral < INV));
e++
){
assert((e - base64Numerals) * 6 + count == length);
assert((p-bitBuffer)*8 + bitsInAccumulator == start + (e-base64Numerals)*6 );
bitAccumulator = (bitAccumulator << 6) | numeral;
@ -175,7 +215,9 @@ unsigned int base64_to_bits(uint8_t * bitBuffer, unsigned int start, unsigned in
if (count == 0) {
assert(bitsInAccumulator < 9);
if (bitsInAccumulator) {
*p = (bitAccumulator << (8 - bitsInAccumulator)) | (*p & lowBitMask[bitsInAccumulator]);
*p =
(bitAccumulator << (8 - bitsInAccumulator))
|(*p & lowBitMask[bitsInAccumulator]);
}
break;
}
@ -190,7 +232,11 @@ unsigned int base64_to_bits(uint8_t * bitBuffer, unsigned int start, unsigned in
Throws exception if that is not what it gets.
Fills the byte buffer exactly.
Returns a uint8_t containing the excess bits of the last numeral in its low order part.*/
uint8_t base64_to_bytes(uint8_t* byteBuffer, uint_fast32_t byteCount, const char* base64Numerals) {
uint8_t base64_to_bytes(
uint8_t* byteBuffer,
uint_fast32_t byteCount,
const char* base64Numerals
) {
auto numeralsCount{ byteCount * 8 / 6 };
auto bitsCount{ numeralsCount * 6 };
auto length{ base64_to_bits(byteBuffer, 0, bitsCount, base64Numerals) };
@ -220,7 +266,8 @@ uint8_t base64_to_bytes(uint8_t* byteBuffer, uint_fast32_t byteCount, const char
// Converts bit a bit buffer into base 2048 BIPS-39 words
// Using the array ar_sz_bip_0039_wordlist defined in localization.cpp
// The largest word in the array is eight characters, but other languages likely have longer words.
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight or eleven.
// Start does not need to be byte aligned, nor does the length need to be a multiple of eight or
// eleven.
void bits2base2048(const uint8_t* bitBuffer, int start, int length, std::span<char>szBipsWords) {
char* Outputsz{ &(szBipsWords[0]) };
if (szBipsWords.size() == 0)throw ;
@ -233,7 +280,9 @@ void bits2base2048(const uint8_t* bitBuffer, int start, int length, std::span<ch
int bitsInAccumulator{ 8 - start };
assert(bitsInAccumulator < 9);
int count{ length - bitsInAccumulator }; //count is bits remaining in buffer, may go negative
// We need to keep track of count, because the last byte lifted out of the buffer may well have to be an incomplete byte, so when count gets down to below 8, we have to special case loading the bitAccumulator.
// We need to keep track of count, because the last byte lifted out of the buffer may well
// have to be an incomplete byte, so when count gets down to below 8, we have to special
// case loading the bitAccumulator.
while (count) {
while (bitsInAccumulator < 11 && count >0)
{
@ -255,11 +304,15 @@ void bits2base2048(const uint8_t* bitBuffer, int start, int length, std::span<ch
bitsInAccumulator -= 11;
const char* psz_Word{ ar_sz_bip_0039_wordlist[wordnumber] };
while (*psz_Word) {
if (Outputsz == end_of_Outputsz)throw FatalException("not enough room for BIPS-0039 passphrase");
if (Outputsz == end_of_Outputsz){
throw FatalException("not enough room for BIPS-0039 passphrase");
}
*Outputsz++ = *psz_Word++;
}
if (bitsInAccumulator + count > 0) {
if (Outputsz == end_of_Outputsz)throw FatalException("not enough room for BIPS-0039 passphrase");
if (Outputsz == end_of_Outputsz){
throw FatalException("not enough room for BIPS-0039 passphrase");
}
*Outputsz++ = ' ';
}
}

View File

@ -1,13 +1,27 @@
#pragma once
// Converts a bit buffer, arbitrarily positioned with respect to byte boundaries, into a
// base sixty four numeral. In release mode, returns without doing anything if the output
// string buffer is too small.
// In debug mode, halts execution with an assert. Should throw, need to put in and
// unit test.
void bits2base64(
const uint8_t * bitBuffer,
unsigned int start,
unsigned int length,
std::span<char>base64Numerals
);
// Converts a bit buffer, arbitrarily positioned with respect to byte boundaries, into a base sixty four numeral.
// In release mode, returns without doing anything if the output string buffer is too small.
// In debug mode, halts execution with an assert. Should throw, need to put in and unit test.
void bits2base64(const uint8_t * bitBuffer, unsigned int start, unsigned int length, std::span<char>base64Numerals);
// Converts a base64 numeral into a bit buffer. The input string is terminated by the first non base64 character, normally a space or null, or terminated when the bit buffer is full. A correct length base64 numeral does not need a terminating space or null.
// If successful, returns a length equal to the bit buffer length. If the input string is too short, returns a length shorter than the bit buffer. If the bit buffer is not a multiple of six bits, and the last base64 numeral of the bit buffer has trailing binary ones, returns a length greater than the size of the bitbuffer, without writing the trailing binary bits into the bit buffer.
unsigned int base64_to_bits(uint8_t* bitBuffer, unsigned int start, unsigned int length, const char* base64Numerals);
// Converts a base64 numeral into a bit buffer. The input string is terminated by the first
// non base64 character, normally a space or null, or terminated when the bit buffer is full.
// A correct length base64 numeral does not need a terminating space or null.
// If successful, returns a length equal to the bit buffer length. If the input string is too
// short, returns a length shorter than the bit buffer. If the bit buffer is not a multiple of six bits, and the last base64 numeral of the bit buffer has trailing binary ones, returns a length greater than the size of the bitbuffer, without writing the trailing binary bits into the bit buffer.
unsigned int base64_to_bits(
uint8_t* bitBuffer,
unsigned int start,
unsigned int length,
const char* base64Numerals
);
/* Expects pointer to byte buffer and pointer to string.
Expects a string of exactly the correct number of numerals,
@ -15,7 +29,11 @@ unsigned int base64_to_bits(uint8_t* bitBuffer, unsigned int start, unsigned int
Throws exception if that is not what it gets.
Fills the byte buffer exactly.
Returns a uint8_t containing the excess bits of the last numeral in its low order part.*/
uint8_t base64_to_bytes(uint8_t* byteBuffer, uint_fast32_t byteCount, const char* base64Numerals);
uint8_t base64_to_bytes(
uint8_t* byteBuffer,
uint_fast32_t byteCount,
const char* base64Numerals
);
/* Expects reference to a range object over bytes and pointer to string.
Expects a string of exactly the correct number of numerals,
@ -25,9 +43,11 @@ uint8_t base64_to_bytes(uint8_t* byteBuffer, uint_fast32_t byteCount, const char
Returns a uint8_t containing the excess bits of the last numeral in its low order part.*/
template < typename T>
std::enable_if_t<
sizeof(std::size(std::declval<T>())) >= sizeof(int) &&
!std::is_pointer<T>::value &&
sizeof(std::declval<T>()[0]) == 1,
uint8_t
>base64_to_bytes( T& byteRange, const char* base64Numerals) {
return base64_to_bytes(static_cast<std::nullptr_t>(&byteRange[0]), static_cast<uint_fast32_t>(std::size(byteRange)), base64Numerals);
return base64_to_bytes(
static_cast<std::nullptr_t>(&byteRange[0]),
static_cast<uint_fast32_t>(std::size(byteRange)), base64Numerals);
}

View File

@ -82,10 +82,6 @@ std::span<uint8_t>& operator^=(std::span<byte>&, byte *);
#define SODIUM_STATIC
#include <sodium.h>
#include <mpir.h>
extern "C" {
int _sodium_sc25519_is_canonical(const unsigned char s[32]);
} //This is a private identifier used by libsodium, which
//outsiders are not supposed to access, but we need it regardless.
#pragma comment(lib, "libsodium.lib")
inline wxString _wx(const char* sz) { return wxString::FromUTF8Unchecked(sz); }
#include "introspection_of_standard_C_types.h"

View File

@ -1,7 +1,7 @@
#include "stdafx.h"
//#include <mbctype.h>
/* Any code here can be deleted at will without impact on the
/* Any code here can be deleted at will without impact on the
functioning of the program.
This routine gets called during every unit test, and can log its results
@ -9,23 +9,42 @@ This routine gets called during every unit test, and can log its results
because useful working code should be moved the appropriate
files, and broken code should be thrown away
Anything here is a residue of forgotten experiments, and can safely
Anything here is a residue of forgotten experiments, and can safely
be thrown away. If any experiments have value, they will be
have been recorded in git. Nothing here is needed. If it was
needed, would have been moved.
If it needs to interact with the outside world, should post a message
Namespace testbed is only defined in this cpp file, hence nothing within
this namespace can be accessed from anywhere else.
except the routine testbed
If it needs to interact with the outside world, should post a message
analogously to queue_error_message, which then calls back to a
routine in this file.*/
namespace testbed {
using ristretto255::hash, ristretto255::hsh, ristretto255::scalar, ristretto255::point, ro::serialize, ro::bin2hex, ro::hex2bin, ro::bin2hex, ro::fasthash,ro::CompileSizedString ;
using ristretto255::hash, ristretto255::hsh, ristretto255::scalar,
ristretto255::point, ro::serialize, ro::bin2hex, ro::hex2bin,
ro::bin2hex, ro::fasthash,ro::CompileSizedString ;
/* experimental code called during unit test
* Anything here is a residue of forgotten experiments,
/* experimental code called during unit test
Anything here is a residue of forgotten experiments,
and can safely be thrown away
This is a playground, where you can do stuff without worrying you might inadvertently break something that matters
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.
Uncaught exceptions result in unit test failure, but not in an error
message in the main program UI.
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.
*/
void testbed() {

View File

@ -1,5 +1,6 @@
#include "stdafx.h"
using ro::msec, ro::msec_since_epoch, ro::bin2hex, ro::to_base64_string, ristretto255::hash, ristretto255::hsh, ristretto255::scalar,
using ro::msec, ro::msec_since_epoch, ro::bin2hex, ro::to_base64_string, ristretto255::hash,
ristretto255::hsh, ristretto255::scalar,
ristretto255::point, ro::base58;
class FailureToThrowExpectedException : public MyException {
@ -81,23 +82,63 @@ static bool EndUnitTest() {
ILogMessage("Passed Unit Test");
}
else {
wxLogMessage(_T("\nFailed Unit Test\nunit test error %d\n%s"), errorCode, _wx(szError.c_str()));
wxLogMessage(_T("\nFailed Unit Test\nunit test error %d\n%s"),
errorCode, _wx(szError.c_str())
);
}
if (singletonApp->m_unit_test && !singletonApp->m_display) {
singletonFrame->Close(); //Shuts down program immediately. When the program is closed, now or later, will return errorCode.
singletonFrame->Close(); //Shuts down program immediately.
// When the program is closed, now or later, will return errorCode.
}
return true;
}
static bool curve_order(void) {
unit_test_action = &EndUnitTest;
next_action = unit_test_action;
ILogMessage("\tChecking mpz arbitrary precision integer conversions to cryptographic values.");
ILogMessage(
"\tChecking mpz arbitrary precision integer conversions to cryptographic values."
);
try {
scalar max(-1);
wxLogMessage(_wx("\t\tmax scalar:\t0x%s"), _wx(bin2hex(max)));
wxString curve_order_as_unreduced_scalar{ _wx(bin2hex(scalar::scOrder)) };
ro::CompileSizedString<sizeof(scalar) * 2> sz;
mpz_get_str(sz, 16, mpir::ristretto25519_curve_order);
wxLogMessage(_wx("\t\tcurve order:\t0x%s"), _wx(sz));
wxString curve_order_as_mpir_bigint{ _wx(sz) };
if (curve_order_as_unreduced_scalar != curve_order_as_mpir_bigint){
throw MyException("inconsistent curve order");
}
scalar sc_zero{ 0 };
scalar sc_one{ 1 };
scalar sc1{ scalar::random() };
scalar sc2{ scalar::random() };
assert(sc1 != sc2);
assert(sc1<sc2 || sc1>sc2);
assert(sc1 < scalar::scOrder);
assert(max < scalar::scOrder);
assert(max > sc_zero);
assert(max + sc_one == scalar(0));
assert(sc_one + max < scalar::scOrder);
assert(!(sc_one < scalar::scOrder + sc_one));
assert(sc_one == scalar::scOrder + sc_one);
assert((sc_one + max < scalar::scOrder));
assert(sc1.valid());
assert(!scalar::scOrder.valid());
assert(!scalar(0).valid());
bool f = true
&& sc1 != sc2
&& sc1<sc2 || sc1>sc2
&& (sc1 < scalar::scOrder
&& max < scalar::scOrder
&& max > sc_zero
&& max + sc_one == sc_zero
&& sc_one + max < scalar::scOrder
&& !(sc_one < scalar::scOrder + sc_one)
&& sc_one == scalar::scOrder + sc_one
&& (sc_one + max < scalar::scOrder)
&& sc1.valid())
&& !scalar::scOrder.valid()
&& !scalar(0).valid();
if (!f)throw MyException("scalar range and validation messed up");
}
catch (const std::exception& e) {
errorCode = 30;
@ -122,7 +163,8 @@ static bool checkDataConversionsProduceExpected(void){
std::array<char, 27>ar_brownfox{ "the quick brown fox jumped" };
char* p_over{ p_brownfox + 27 };
wxString wx_over{ p_over };
*--ar_brownfox.end() = ' '; //fixes the trailing nul appended to ar_brownfox, so that it is no longer a null terminated string.
*--ar_brownfox.end() = ' '; //fixes the trailing nul appended to ar_brownfox, so that it is
// no longer a null terminated string.
wxString wx_jumped(&ar_brownfox[0], std::size(ar_brownfox));
hsh hsh1;
hsh hsh2;
@ -144,30 +186,40 @@ static bool checkDataConversionsProduceExpected(void){
scalar scl_b{ scalar::random() };
point pt_a{ scl_b.timesBase() };
std::string str_pt_a{ &(base58(pt_a))[0] };
if (pt_a != base58<point>::bin(str_pt_a.c_str()))MyException("Round trip from and two base 58 representation failed");
if (pt_a != base58<point>::bin(str_pt_a.c_str())){
MyException("Round trip from and two base 58 representation failed");
}
}
{
point pt_b;
try {
pt_b = base58<point>::bin("galvhAmKLYPjiEUbiDvE54pnKhhdYSxC3G5p9czHPuKaB not base 58 because uses 'l'");
pt_b = base58<point>::bin(
"galvhAmKLYPjiEUbiDvE54pnKhhdYSxC3G5p9czHPuKaB not base 58 because uses 'l'"
);
throw FailureToThrowExpectedException();
assert(false);
}
catch (const NotBase58Exception&) {}
try {
pt_b = base58<point>::bin("AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrS wrong checksum");
pt_b = base58<point>::bin(
"AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrS wrong checksum"
);
throw FailureToThrowExpectedException();
assert(false);
}
catch (const BadStringRepresentationOfCryptoIdException&) {}
try {
pt_b = base58<point>::bin("MaevYgZimDjzQWxbnHcNdMqkMVmjWpYEqippTfj9y6U7sM overlong error");
pt_b = base58<point>::bin(
"MaevYgZimDjzQWxbnHcNdMqkMVmjWpYEqippTfj9y6U7sM overlong error"
);
throw FailureToThrowExpectedException();
assert(false);
}
catch (const OversizeBase58String&) {}
}
hash<512>hash_a{ ar_brownfox, wx_over.ToUTF8(), hash1,base58<point>::bin("AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrP") };
hash<512>hash_a{ ar_brownfox, wx_over.ToUTF8(), hash1,base58<point>::bin(
"AWyxo34SopBAtWy9qH6RfheK3xUCmu9fgF4ZjHwT9RVrP"
) };
std::string str_hash_a{ &(base58(hash_a))[0] };
scalar scl_a{ hash_a };
std::string str_sclr_a = &(base58(scl_a))[0];
@ -176,7 +228,11 @@ static bool checkDataConversionsProduceExpected(void){
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");
if (base58(hash_b).operator std::string() !=
"i22EVNPsKRjdxYTZrPPu9mx6vnrBjosFix5F4gn2mb2kF"
){
throw MyException("unexpected hash of transformations");
}
}
catch (const std::exception& e) {
errorCode = 27;
@ -259,7 +315,8 @@ static bool TestDelayedErrorDialog(void) {
unit_test_action = &Waiting_for_delayed_dummy_error_message;
next_action = &CheckForUtfEnvironment;
ILogMessage("\t\tthrowing exception");
throw UnitTestOfException(); //If we throw exception before setting the next unit test, this unit test gets endless recalled.
throw UnitTestOfException(); //If we throw exception before setting the next unit test,
// this unit test gets endless recalled.
return true;
}
@ -297,10 +354,16 @@ static bool WalletCreationUI(void) {
static bool OpenWallet(void) {
try {
/* If LastUsed is the empty string, check if default filename exists. If it exists, set Last Used to default file name
If LastUsed is not the empty string, or no longer the empty string, attempt to open indicated file. If open fails, or reading the opened file produces bad results, abort with exception
If LastUsed is still the empty string, attempt to create default filename. If creation fails, abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during initialization, should bring up the more complex UI for constructing or selecting your wallet file.*/
/* If LastUsed is the empty string, check if default filename exists. If it exists, set
Last Used to default file name
If LastUsed is not the empty string, or no longer the empty string, attempt to open
indicated file. If open fails, or reading the opened file produces bad results, abort with
exception
If LastUsed is still the empty string, attempt to create default filename. If creation fails,
abort with exception. If it succeeds, set LastUsed to default filename.
The exception in unit test should simply generate an error message, but if run during
initialization, should bring up the more complex UI for constructing or selecting your
wallet file.*/
ILogMessage("\tWallet file");
assert(singletonApp->pConfig);
singletonApp->pConfig->SetPath(_T("/Wallet"));
@ -336,8 +399,12 @@ static bool OpenWallet(void) {
auto pubkey = read_keys.column<ristretto255::point>(0);
auto id = read_keys.column<int>(1);
auto use = read_keys.column<int>(2);
if (use != 1)throw MyException(R"|(Unknown secret key algorithm index in "Names" table)|");
if (!read_name(id)) throw MyException(R"|(No entry corresponding to public key in "Names" table)|");
if (use != 1){
throw MyException(R"|(Unknown secret key algorithm index in "Names" table)|");
}
if (!read_name(id)){
throw MyException(R"|(No entry corresponding to public key in "Names" table)|");
}
const char* name = read_name.name();
if(MasterSecret(name).timesBase()!=*pubkey)throw MyException(R"|(Public key of name fails to correspond)|");
wxLogMessage(_T("\t\t\"%s\" has expected public key 0x%s"), name, (wxString)(bin2hex(*pubkey)));
@ -714,9 +781,9 @@ static bool TestShareSecretGenerationSpeed(void) {
|| !(true
&& !point::ptZero
&& false == !point::ptBase
&& !scalar(0)
&& false == !scalar(1)
&& false == !scalar::random()
&& scalar(0).valid()==false
&& scalar(1).valid()
&& scalar::random().valid()
&& !ristretto_test_vector[0x00]
&& point::ptBase * 6 == ristretto_test_vector[0x06]
&& scalar(7) * point::ptBase == ristretto_test_vector[0x07]
@ -735,7 +802,8 @@ static bool TestShareSecretGenerationSpeed(void) {
&& scalar(3) == scalar(2) + scalar(1)
&& scalar(3) == scalar(4) + scalar(-1)
&& scalar(0x0103) == scalar(0xFE) + scalar(5)
&& !scalar::random() == false
&& scalar(0xDEADBEEF) == scalar(0xDEADBDDE) + scalar(0x111)
&& scalar(0xBEEFDEADBEEF) == scalar(0xBEEFDEADBDDE) + scalar(0x111)
)
)
{