1
0
forked from cheng/wallet
wallet/src/ristretto255.h

504 lines
20 KiB
C
Raw Normal View History

#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::is_serializable,
ro::serialize, ro::bin2hex, ro::hex2bin,
ro::bin2hex, ro::CompileSizedString,
ro::serializable;
;
// 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);
}
template<serializable 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<serializable 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(!serializable<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,
// it finalizes the hash. (see hsh)
// Usage
// hash(a, b, c ...);
// hash and hsh serialize all their arguments, converting them into machine
// and compiler independent form. If they don't know how to serialize an
// argument type, you get a compile time error. 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());
// C array arguments after the first are vulnerable to array decay to pointer, so use hsh and "<<"
// for them. or wrap them in std::span(OldTypeCarray)
// It always a wise precaution to not use old type C arays, or wrap them
// in a span.
// Old type zero terminated utf8 strings work. The trailing zero is included.
// 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."
);
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.
explicit hash(hsh<hashsize>& in) {
int i = crypto_generichash_blake2b_final(
&in.st,
&blob[0], hashsize / 8);
assert(i == 0);
if (i) throw HashReuseException();
}
template< serializable T, typename... Args>explicit hash(const T& first, Args... args) {
hsh<hashsize> in;
in << first;
if constexpr (sizeof...(args) > 0) {
in.hashinto(args...);
}
int i = crypto_generichash_blake2b_final(
&in.st,
&blob[0], hashsize / 8);
assert(i == 0);
}
hash& operator=(hsh<hashsize>&& in) {
int i = crypto_generichash_blake2b_final(
&in.st,
&blob[0], hashsize / 8);
if (i) throw HashReuseException();
}
bool operator==(const hash<hashsize>& pt) const& {
return blob == pt.blob; //Do not need constant time equality test on hashes
}
bool operator!=(const hash<hashsize>& pt) const& {
return blob != pt.blob; //Do not need constant time equality test on hashes
}
};
// 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]); }
2022-05-23 02:06:01 -04:00
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
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::is_serializable<scalar&>::value);
static_assert(ro::is_serializable<hash<512>&>::value);
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::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::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
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&);