#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 in); void randombytes_buf(std::span in); namespace ristretto255 { using ro::to_base64_string, ro::is_serializable, 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; templateclass hsh; templateclass hash; template decltype(std::declval().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 decltype(std::declval().blob, wxString()) to_wxString(const T& p_blob) { std::array sz; bits2base64(&(p_blob.blob[0]), 0, sizeof(p_blob.blob) * 8, std::span(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 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 hsh& 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 hsh& operator << (const T& j) { auto sj = ro::serialize(j); auto i = crypto_generichash_blake2b_update( &(this->st), (const unsigned char*)&sj[0], sj.size() ); if (i) throw HashReuseException(); return *this; } }; static_assert(!has_machine_independent_representation >, "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 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; public: static constexpr unsigned int type_indentifier = 2 + hashsize / 64; hash() = default; std::array 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& in) { int i = crypto_generichash_blake2b_final( &in.st, &blob[0], hashsize / 8); assert(i == 0); if (i) throw HashReuseException(); } template< has_machine_independent_representation T, typename... Args>explicit hash(const T& first, Args... args) { hsh 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&& in) { int i = crypto_generichash_blake2b_final( &in.st, &blob[0], hashsize / 8); if (i) throw HashReuseException(); } bool operator==(const hash& pt) const& { return blob == pt.blob; //Do not need constant time equality test on hashes } bool operator!=(const hash& 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 blob; static_assert( std::is_trivially_copyable>(), "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&& in) : blob{ std::forward>(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 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&& in) : blob{ in } {}; explicit constexpr scalar(std::array* in) :blob(*in) {}; explicit constexpr scalar(uintmax_t k){ for (auto& j : blob) { j = k; k = k >> 8; } } template explicit constexpr scalar(std::enable_if_t < ro::is_standard_unsigned_integer, T> i) :scalar(uintmax_t(i)) {} template , uintmax_t>> explicit constexpr scalar(T i) : scalar(U(ro::iabs(i))) { static_assert (ro::is_standard_signed_integer); 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 && *thisconst& ac) { auto p = blob.rbegin(); for (auto x : ac) { *p++ = x; } } }; static_assert(ro::blob_type && !ro::blob_type); static_assert(ro::is_blob_field_type::value); static_assert(ro::is_blob_field_type::value); static_assert(ro::is_blob_field_type::value); static_assert(ro::is_blob_field_type >::value); static_assert(false == ro::is_blob_field_type::value); static_assert(ro::has_machine_independent_representation); static_assert(ro::has_machine_independent_representation&>); static_assert(ro::is_blob_field_type::value == false); static_assert(ro::has_machine_independent_representation); static_assert(ro::has_machine_independent_representation); static_assert(ro::has_machine_independent_representation == false); //false because uint8_t * has no inband terminator static_assert(false == ro::has_machine_independent_representation && !ro::is_constructible_from_v, wxString>, "wxStrings are apt to convert anything to anything, with surprising and unexpected results"); static_assert(ro::has_machine_independent_representation().ToUTF8())> == true); static_assert(ro::is_constructible_from_all_of, std::array>); static_assert(ro::is_constructible_from_all_of, 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, hash<256>>); static_assert(false == ro::is_constructible_from_any_of , byte*>, "do not want indiscriminate casts"); static_assert(false == ro::is_constructible_from_any_of , byte*>, "do not want indiscriminate casts "); static_assert(false == ro::is_constructible_from_v, float>); static_assert(ro::has_machine_independent_representation == 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())[0])) == 1); static_assert (std::is_standard_layout(), "scalar for some reason is not standard layout"); static_assert (std::is_trivial(), "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 txt; scalar c; scalar s; point K; signed_text( const scalar&, // Signer's secret key const point&, // Signer's public key std::span // 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 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 (const ro::CompileSizedString< (2 * sizeof(ristretto255::scalar))>&); template<> ro::CompileSizedString< (2 * sizeof(ristretto255::scalar)) > ro::bin2hex(const ristretto255::scalar&); template<> ro::CompileSizedString< (8 * sizeof(ristretto255::scalar) + 5) / 6> ro::to_base64_string (const ristretto255::scalar&);