1
0
forked from cheng/wallet
wallet/src/ristretto255.h
Cheng d59729f396
Never really figured out why my code was breaking
fixed it by looking for funny things that deviated from
the sameples,  and doing various recommended safe things,
and found a few sql errors, and one by one the crashes
went away.

The new wxWidgets just seems less tolerant of little careless
stuff that is not right.
2023-10-18 20:23:56 +10:00

528 lines
22 KiB
C++

#pragma once
// libsodium is typeless, which I find confusing. Everything is unsigned
// bytes pointed at by naked pointers. It is well written assembly language
// written in C It is not even C written in C, let alone C++ written in C.
//
// NaCl provides a high level C++ interface to the low level C libsodium
// interface, but I just don't like the people running NaCl and suspect them
// of being in bed with my enemies, whereas I do like the people running
// LibSodium.
//
// It is nonetheless probably stupid to write my own high level interface to
// LibSodium, when one has already been written, but I am going to do so
// anyway, because elliptic curves are just way cool, and because ...
// because ... I am having trouble thinking up a good excuse ... ah yes,
// because when I read the NaCl documentation it says "X has not been
// implemented yet" when X is something that is pretty easy to implement
// if one has direct access to elliptic curve objects, and our Merkle patricia
// blockchain is going to be a great big pile of elliptic curve objects.
// NaCl suffers from the chronic disease both of open source projects,
// and also of high level interfaces to low level data structures, of getting
// fine tuned to the implementer's pet projects, which fine tuning is apt to
// get in the way of someone else's pet project.
//
// LibSodium, being assembly language written in C, rather than C written
// in C, or C++ written in C, has the problems:
// If you mix up the pointer to one kind of object with the pointer to
// another kind of object, you are sol
// If you mix up a pointer to a thirty two byte object with a pointer to a
// sixtyfour byte object, you are really sol
// if you reference something that has been de-allocated, you are sol.
// If you reference something that has not been properly initialized,
// you are sol.
// It is all raw memory, and structure exists only in the head of the
// programmer. The compiler knows nothing of these structures.
//
// That said, there is an immense amount of cryptographic knowledge
// encapsulated in this library, and I need to lift that knowledge into the
// language of C++20, from C that is almost assembly.
// There is a huge amount of knowledge embedded, and translating it
// from what is almost a machine language representation to a C++20
// representation involves a big pile of stuff.
// We are going to need to lift
// https://paragonie.com/blog/2017/06/libsodium-quick-reference-quick-comparison-similar-functions-and-which-one-use or their ristretto equivalents to C++
// Starting with ristretto points and scalars, but they are useless without a
// pile of other things, many of those other things being in
// https://download.libsodium.org/doc/helpers/
// I went there to find out what the hell "sodium_is_zero" means, but
// found a pile of other things that I am going to need, and got distracted
// by no end of odds and ends that I am going to need to be to lift to
// C++20 in order for ristretto points and scalars to be put to any use.
void randombytes_buf(std::span<byte> in);
void randombytes_buf(std::span<char > in);
namespace ristretto255 {
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;
// a class representing ristretto255 scalars
class scalar;
template<unsigned int>class hsh;
template<unsigned int>class hash;
template <class T>decltype(std::declval<T>().blob, bool()) ConstantTimeEqualityTest(const T& x, const T& y) {
if (T::constant_time_required) {
return 0 == sodium_memcmp(&x.blob[0], &y.blob[0], sizeof(x.blob));
}
else return x == y;
}
template <class T>decltype(std::declval<T>().blob, wxString()) to_wxString(const T& p_blob) {
std::array<char, (sizeof(p_blob.blob) * 8 + 11) / 6> sz;
bits2base64(&(p_blob.blob[0]), 0, sizeof(p_blob.blob) * 8, std::span<char>(sz));
return wxString::FromUTF8Unchecked(&sz[0]);
}
// hsh constructs a partial hash, not yet finalized
// normally never explicitly used in code, but rather a nameless rtype value on the stack.
// To which more stuff can be added with the << operator
// usage:
// hash( hsh(a, b) << c << f << g );
// assert( hash(a, b, c, d, e) == hash(hsh(a, b, c) << d << e) );
// hash finalizes hsh.
// Of course you would never use it that way, because you would only
// use it explicitly if you wanted to keep it around
// attempting hash more things into an hsh object that has already
// been finalized will throw an exception.
// Old type C byte array arguments after the first are vulnerable to
// array decay to pointer, so wrap them in std::span(OldTypeCarray)
// or hash them using "<<" rather than putting them in the initializer.
// 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, 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.");
static constexpr unsigned int type_indentifier = 2 + (hashsize + 0x90 * 8) / 64;
static_assert(crypto_generichash_BYTES_MAX < 0x90, "Change in max hash has broken our type ids");
crypto_generichash_blake2b_state st;
hsh() {
int i{ crypto_generichash_blake2b_init(
&st,
nullptr,0,
hashsize / 8)
};
assert(i == 0);
}
operator hash<hashsize>();
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(
&(this->st),
(const unsigned char*)&sj[0],
sj.size()
);
if (i) throw HashReuseException();
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(); //Bug, the library always returns 0 no matter what misuse has happened.
return *this;
}
};
static_assert(!has_machine_independent_representation<hsh<256> >, "Don't want to partially hash partial hashes");
// This constructs a finalized hash.
//
// Usage:
// hash c(x, y, z ...);
// creates the variable c, and initializes and finalizes it with the hash of x, y, z ...
//
// is equivalent to
// hsh b();
// b << x << y; b << z << ...; //unfinalized hash
// hash c(b); //creates c and finalizes b
// // Any subsequent operations on b should cause a run time exception.
//
// and equivalent to
// hash<256> c = hsh() << x << y << z << ...;
//
// and equivalent to
// hash<256> c(hsh() << x << y << z << ... )
//
// hash and hsh serialize all their arguments, converting them into machine,
// compiler, and locale independent form. If they don't know how to serialize
// an argument type, you get a compile time error.
// "the concept 'ro::has_machine_independent_representation<...>' evaluated to false"
//
// Suppose you don't want hsh finalized, perhaps because you are creating many
// hashes each with same very lengthy preamble but different suffixes: Then:
// hsh(b);
// b << preamble;
// hash cfoo = hsh(b) << suffix_foo;
// hash cbar = hsh(b) << suffix_bar;
//
// To serialize a new type,
// create a new overload for the function "serialize"
// to hash a wxString, use its ToUTF8 member
// wxString wxsHello(L"Hello world");
// hash hashHello(wxsHello.ToUTF8());
// Old type C array arguments after the first are vulnerable to array decay
// to pointer, which will generate a compile time error. If this happens use
// hsh and "<<" for them. or wrap them in std::span
// It always a wise precaution to not use old type C arays, or wrap them
// in a span.
// std::strings and old type zero terminated utf8 strings work. The trailing
// zero is included in the hash so that hash("tea", "spoon") != hash("teaspoon")
// The program should by running in the UTF8 locale, attempts to set that
// locale on startup. and tests for success in the unit test, so that the
// same string will yield the same hash no matter where in the world the code
// is running.
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."
);
friend point;
friend scalar;
friend hsh<hashsize>;
public:
static constexpr unsigned int type_indentifier = 2 + hashsize / 64;
hash() = default;
std::array<uint8_t, hashsize / 8> blob;
~hash() = default;
hash(hash&&) = default; // Move constructor
hash(const hash&) = default; // Copy constructor
hash& operator=(hash&&) = default; // Move assignment.
hash& operator=(const hash&) = default; // Copy assignment.
bool operator==(const hash&) const = default; //Do not need constant time equality test on hashes
auto operator<=>(const hash&) const = delete; //ordering operation on hashes makes no sense.
explicit hash(hsh<hashsize>& in) {
int i = crypto_generichash_blake2b_final(
&in.st,
&blob[0], hashsize / 8);
assert(i == 0);
if (i) throw HashReuseException(); //Bug, the library always returns 0 no matter what misuse has happened.
}
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( ro::trigger_error(args)...);
}
int i = crypto_generichash_blake2b_final(
&in.st,
&blob[0], hashsize / 8);
if (i) throw HashReuseException(); //Bug, the library always returns 0 no matter what misuse has happened.
}
};
template<unsigned int hashsize = 256>
hsh<hashsize>::operator hash<hashsize>() {
return hash(*this);
}
// a class representing ristretto255 elliptic points,
// 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 base58 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 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.
bool valid(void) const { return 0 != crypto_core_ristretto255_is_valid_point(&blob[0]); }
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
point(const point&) = default; // Copy constructor
point& operator=(point&&) = default; // Move assignment.
point& operator=(const point&) = default; // Copy assignment.
bool operator==(const point& pt) const& {
return ConstantTimeEqualityTest(*this, pt);
}
point operator+(const point &pt) const& {
point me;
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;
}
point operator-(const point& pt) const& {
point me;
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;
static_assert(sizeof(blob) == 32, "alignment?");
}
point operator*(const scalar&) const &noexcept;
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"
);
// 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);
}
static point random(void) {
point me;
crypto_core_ristretto255_random(_Out_ &(me.blob[0]));
return me;
}
bool operator !() const& {
return 0 != sodium_is_zero(&blob[0], sizeof(blob));
}
static bool constant_time_required;
};
class scalar
{
friend point;
public:
static constexpr unsigned int type_indentifier = 2;
std::array<uint8_t, crypto_core_ristretto255_SCALARBYTES> blob;
static_assert(sizeof(blob) == 32, "watch for size and alignment bugs. Everyone should standarize on 256 bit secret keys except for special needs");
explicit scalar() = default;
~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 constexpr scalar(uintmax_t k){ for (auto& j : blob) { j = k; k = k >> 8; } }
template <class T>
explicit constexpr scalar(std::enable_if_t < ro::is_standard_unsigned_integer<T>, T> i) :scalar(uintmax_t(i)) {}
template <class T, class U = std::enable_if_t<ro::is_standard_signed_integer<T>, uintmax_t>>
explicit constexpr scalar(T i) : scalar(U(ro::iabs<T>(i))) {
static_assert (ro::is_standard_signed_integer<T>);
if (i < 0) crypto_core_ristretto255_scalar_negate(&blob[0], &blob[0]);
}
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);
}*/
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( _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(_Out_&me.blob[0], &blob[0]);
assert(i == 0);
if (i != 0)throw NonRandomScalarException();
return me;
}
scalar operator-(const scalar& sclr) const& {
scalar me;
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(_Out_&me.blob[0], &blob[0], &sclr.blob[0]);
return me;
}
scalar operator/(const scalar sclr) const& {
return operator*(sclr.multiplicative_inverse());
}
scalar operator*(int64_t i) const& {
return operator * (scalar(i));
}
point operator*(const point& pt) const& {
point me;
auto i{ crypto_scalarmult_ristretto255(_Out_&me.blob[0], &blob[0], &pt.blob[0]) };
assert(i == 0);
if (i != 0)throw NonRandomScalarException();
return me;
}
point timesBase() const& {
point me;
auto i{ crypto_scalarmult_ristretto255_base(_Out_ & me.blob[0], &blob[0]) };
assert(i == 0);
if (i != 0)throw NonRandomScalarException();
return me;
}
explicit scalar(const hash<512>& x) {
static_assert(crypto_core_ristretto255_HASHBYTES == 64, "inconsistent use of magic numbers");
crypto_core_ristretto255_scalar_reduce(&blob[0], &(x.blob)[0]);
}
static scalar random(void) {
scalar me;
crypto_core_ristretto255_scalar_random(_Out_ & me.blob[0]);
return me;
}
bool valid(void) const& {
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
}
// 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();
for (auto x : ac) {
*p++ = x;
}
}
};
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::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::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 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::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);
static_assert (std::is_standard_layout<scalar>(), "scalar for some reason is not standard layout");
static_assert (std::is_trivial<scalar>(), "scalar for some reason is not trivial");
static_assert(sizeof(point) == 32, "funny alignment");
static_assert(sizeof(scalar) == 32, "funny alignment");
class signed_text {
public:
std::span<char> txt;
scalar c;
scalar s;
point K;
signed_text(
const scalar&, // Signer's secret key
const point&, // Signer's public key
std::span<char> // Text to be signed.
);
bool verify();
};
class CMasterSecret :public scalar {
public:
CMasterSecret() = default;
CMasterSecret(const scalar& pt) :scalar(pt) {}
CMasterSecret(const scalar&& pt) :scalar(pt) {}
CMasterSecret(CMasterSecret&&) = default; // Move constructor
CMasterSecret(const CMasterSecret&) = default; // Copy constructor
CMasterSecret& operator=(CMasterSecret&&) = default; // Move assignment.
CMasterSecret& operator=(const CMasterSecret&) = default; // Copy assignment.
template<class T> auto operator()(T psz) {
scalar& t(*this);
return scalar(hash<512>(t, psz));
}
};
} //End ristretto255 namespace
// Ristretto255 scalars are defined to be little endian on all hardware
// regardless of the endianess of the underlying hardware.
// though it is entirely possible that sometime in the future, this
// definition will be changed should big endian hardware ever be
// sufficiently popular for anyone to care.
// So, because scalars are in fact integers, displaying them as
// biendian on all hardware when displayed in hex
// or base64. Everything else gets displayed in memory order.
template<> ristretto255::scalar ro::hex2bin <ristretto255::scalar >(const ro::CompileSizedString< (2 * sizeof(ristretto255::scalar))>&);
template<> ro::CompileSizedString< (2 * sizeof(ristretto255::scalar)) > ro::bin2hex<ristretto255::scalar>(const ristretto255::scalar&);
template<> ro::CompileSizedString< (8 * sizeof(ristretto255::scalar) + 5) / 6> ro::to_base64_string <ristretto255::scalar>(const ristretto255::scalar&);