forked from cheng/wallet
184 lines
6.1 KiB
C++
184 lines
6.1 KiB
C++
#pragma once
|
|
/* This header handles the stuff related to arbitrary precision arithmetic values*/
|
|
|
|
namespace mpir {
|
|
class mpz {
|
|
mpz_t mpz_m;
|
|
public:
|
|
mpz() {
|
|
mpz_init2(mpz_m, 624);
|
|
}
|
|
mpz(const char* str, int base) {
|
|
mpz_init(mpz_m);
|
|
if (mpz_set_str(mpz_m, str, base))throw BadDataException();
|
|
}
|
|
~mpz() {
|
|
mpz_clear(mpz_m);
|
|
}
|
|
operator mpz_ptr() {
|
|
return mpz_m;
|
|
}
|
|
};
|
|
|
|
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.
|
|
// This explicit construction and destruction is a workaround because C++ lacks
|
|
// support for concurrent processes
|
|
// and is therefore unable to correctly handle non pod thread local objects. It will
|
|
// not correctly construct and
|
|
// destruct thread_local objects by itself, because the model machine does not have a model
|
|
// for threading. It has a pile of matchsticks and a tub of glue with which you can build
|
|
// your own model. It supports all the stuff you need for threads, but has no idea how all
|
|
// these moving parts fit together.
|
|
// So you have to construct and destruct your non pod thread local objects in code.
|
|
class thread_local__ {
|
|
public:
|
|
// These exist to avoid the high cost of repeatedly creating and destroying temporary mpz objects.
|
|
// They are expensive to create and destroy, because they use heap allocation.
|
|
// These are used all over the place as temporaries.
|
|
mpir::mpz q, r, n;
|
|
};
|
|
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
|
|
|
|
namespace ro {
|
|
using ristretto255::scalar, ristretto255::point;
|
|
|
|
auto fasthash(uint64_t, std::span<const uint64_t>)->uint32_t;
|
|
void right_justify_string(char*, char*);
|
|
bool is_alphanumeric_fixed_length(unsigned int, const char*);
|
|
|
|
template <class T> typename std::enable_if<
|
|
ro::is_blob_field_type<T>::value,
|
|
decltype(T::type_indentifier, uint32_t())
|
|
>::type fasthash(const T& p_blob) {
|
|
static_assert(sizeof(T) % 8 == 0, "fasthash assumes a multiple of 8 bytes");
|
|
return fasthash(
|
|
T::type_indentifier,
|
|
std::span< const uint64_t >(
|
|
reinterpret_cast<const uint64_t*>(&p_blob.blob[0]),
|
|
p_blob.blob.size() / 8
|
|
)
|
|
);
|
|
}
|
|
|
|
void map_base_from_mpir58(char*);
|
|
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)> {
|
|
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.
|
|
base58() = default;
|
|
~base58() = default;
|
|
base58(const T&);
|
|
base58(const char* p);
|
|
static const char* bin(
|
|
typename const decltype(T::type_indentifier, char())* p,
|
|
T& sc
|
|
);
|
|
static void bin(const base58<T>& str, T& sc);
|
|
static T bin(const char* str) {
|
|
T sc;
|
|
bin(str, sc);
|
|
return sc;
|
|
};
|
|
T bin() const {
|
|
T sc;
|
|
bin(*this, sc);
|
|
return sc;
|
|
};
|
|
operator T() const {
|
|
return bin();
|
|
}
|
|
};
|
|
|
|
template <class T> typename const decltype(base58<T>::length, T::type_indentifier, uint32_t())
|
|
// cannot be consteval or constexpr, because has to be called after the mpir temp values are constructed
|
|
check_range() {
|
|
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>) {
|
|
mpz_fdiv_q(thl->q, thl->n, mpir::ristretto25519_curve_order);
|
|
}
|
|
else {
|
|
mpz_fdiv_q_2exp(thl->q, thl->n, sizeof(T) * 8);
|
|
}
|
|
assert(mpz_cmp_ui(thl->q, UINT32_MAX) <= 0);
|
|
return static_cast<uint32_t>(mpz_get_ui(thl->q));
|
|
}
|
|
|
|
template <class T> const uint32_t check_range_m{ check_range<T>() };
|
|
|
|
template <class T> const char* base58<T>::bin(
|
|
typename const decltype(T::type_indentifier, char())* p,
|
|
T& sc
|
|
) {
|
|
const uint32_t range = check_range_m<T>;
|
|
base58 strsc;
|
|
char* ps = strsc;
|
|
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>) {
|
|
mpz_fdiv_qr(thl->q, thl->r, thl->n, mpir::ristretto25519_curve_order);
|
|
}
|
|
else {
|
|
mpz_fdiv_q_2exp(thl->q, thl->n, sizeof(sc.blob) * 8);
|
|
mpz_fdiv_r_2exp(thl->r, thl->n, sizeof(sc.blob) * 8);
|
|
}
|
|
size_t count;
|
|
mpz_export(&(sc.blob[0]), &count, -1, 1, -1, 0, thl->r);
|
|
if (count < sizeof(sc.blob))memset(&sc.blob[count], 0, sizeof(sc.blob) - count);
|
|
mpir_ui ck{ (static_cast<uint64_t>(fasthash(sc)) * static_cast<uint64_t>(range)) >> 32 };
|
|
if (ck != mpz_get_ui(thl->q)) throw BadStringRepresentationOfCryptoIdException();
|
|
return p + base58<T>::length;
|
|
}
|
|
|
|
template <class T> char* to_base58(
|
|
// does no string memory allocation, p has to point into a buffer,
|
|
// return value points to next position in the buffer, which is now null
|
|
typename decltype(check_range_m<T>, char())* p,
|
|
const T& sc
|
|
) {
|
|
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>) {
|
|
mpz_addmul_ui(thl->n, mpir::ristretto25519_curve_order, ck);
|
|
}
|
|
else {
|
|
mpz_set_ui(thl->r, ck);
|
|
mpz_mul_2exp(thl->r, thl->r, sizeof(sc.blob) * 8);
|
|
mpz_add(thl->n, thl->n, thl->r);
|
|
}
|
|
mpz_get_str(p, 58, thl->n);
|
|
char* terminal_null{ p + sizeof(base58<T>) - 1 };
|
|
right_justify_string(p, terminal_null);
|
|
map_base_from_mpir58(p);
|
|
return terminal_null;
|
|
// return value points to trailing null
|
|
}
|
|
|
|
template <class T> base58<T>::base58(const T& el) {
|
|
to_base58<T>(static_cast<char*>(*this), el);
|
|
}
|
|
|
|
template <class T> base58<T>::base58(const char* p) {
|
|
memmove(this, p, (this->length));
|
|
std::array<char, (this->length)> test;
|
|
map_base_to_mpir58(this, test, this->length); //Force an exception for bad char
|
|
if (p[this->length] > ' ')throw OversizeBase58String();
|
|
this->operator char* [this->length] = '\0';
|
|
}
|
|
}
|