mirror of
https://gitlab.com/pulsechaincom/erigon-pulse.git
synced 2024-12-27 22:28:21 +00:00
c4805e0262
* issue/issue-281-create_binding_to_pedersen_hash * Add //nolint * Add more nolints * move nolint * Remove nolit * Add gcc install * Upd .ci * Remove staticcheck * Add envs * try to exclude pedersen_hash from test * try to fix mac os build * Add include for mac os * Add include for mac os * Fix runner_os * remove test for macos * Change restrictions * restrict tests to ubuntu * Try test windows * Add build constraint
285 lines
8.3 KiB
C++
285 lines
8.3 KiB
C++
#include <iomanip>
|
|
#include <ios>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <tuple>
|
|
|
|
#include "math.h"
|
|
|
|
namespace starkware {
|
|
|
|
template <size_t N>
|
|
BigInt<N> BigInt<N>::RandomBigInt(Prng* prng) {
|
|
std::array<uint64_t, N> value{};
|
|
for (size_t i = 0; i < N; ++i) {
|
|
gsl::at(value, i) = prng->RandomUint64();
|
|
}
|
|
return BigInt(value);
|
|
}
|
|
|
|
template <size_t N>
|
|
template <size_t K>
|
|
constexpr BigInt<N>::BigInt(const BigInt<K>& v) noexcept : value_{} {
|
|
static_assert(N > K, "trimming is not supported");
|
|
for (size_t i = 0; i < K; ++i) {
|
|
gsl::at(value_, i) = v[i];
|
|
}
|
|
|
|
for (size_t i = K; i < N; ++i) {
|
|
gsl::at(value_, i) = 0;
|
|
}
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr std::pair<BigInt<N>, bool> BigInt<N>::Add(const BigInt& a, const BigInt& b) {
|
|
bool carry{};
|
|
BigInt r{0};
|
|
|
|
for (size_t i = 0; i < N; ++i) {
|
|
__uint128_t res = static_cast<__uint128_t>(a[i]) + b[i] + carry;
|
|
carry = (res >> 64) != static_cast<__uint128_t>(0);
|
|
r[i] = static_cast<uint64_t>(res);
|
|
}
|
|
|
|
return std::make_pair(r, carry);
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr BigInt<2 * N> BigInt<N>::operator*(const BigInt<N>& other) const {
|
|
constexpr auto kResSize = 2 * N;
|
|
BigInt<kResSize> final_res = BigInt<kResSize>::Zero();
|
|
// Multiply this by other using long multiplication algorithm.
|
|
for (size_t i = 0; i < N; ++i) {
|
|
uint64_t carry = static_cast<uint64_t>(0U);
|
|
for (size_t j = 0; j < N; ++j) {
|
|
// For M == UINT64_MAX, we have: a*b+c+d <= M*M + 2M = (M+1)^2 - 1 ==
|
|
// UINT128_MAX. So we can do a multiplication and an addition without an
|
|
// overflow.
|
|
__uint128_t res = Umul128((*this)[j], other[i]) + final_res[i + j] + carry;
|
|
carry = gsl::narrow_cast<uint64_t>(res >> 64);
|
|
final_res[i + j] = gsl::narrow_cast<uint64_t>(res);
|
|
}
|
|
final_res[i + N] = static_cast<uint64_t>(carry);
|
|
}
|
|
return final_res;
|
|
}
|
|
|
|
template <size_t N>
|
|
BigInt<N> BigInt<N>::MulMod(const BigInt& a, const BigInt& b, const BigInt& modulus) {
|
|
const BigInt<2 * N> mul_res = a * b;
|
|
const BigInt<2 * N> mul_res_mod = mul_res.Div(BigInt<2 * N>(modulus)).second;
|
|
|
|
BigInt<N> res = Zero();
|
|
|
|
// Trim mul_res_mod to the N lower limbs (this is possible since it must be smaller than modulus).
|
|
for (size_t i = 0; i < N; ++i) {
|
|
res[i] = mul_res_mod[i];
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
template <size_t N>
|
|
BigInt<N> BigInt<N>::InvModPrime(const BigInt& prime) const {
|
|
ASSERT(*this != BigInt::Zero(), "Inverse of 0 is not defined.");
|
|
return GenericPow(
|
|
*this, (prime - BigInt(2)).ToBoolVector(), BigInt::One(),
|
|
[&prime](const BigInt& multiplier, BigInt* dst) { *dst = MulMod(*dst, multiplier, prime); });
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr std::pair<BigInt<N>, bool> BigInt<N>::Sub(const BigInt& a, const BigInt& b) {
|
|
bool carry{};
|
|
BigInt r{};
|
|
|
|
for (size_t i = 0; i < N; ++i) {
|
|
__uint128_t res = static_cast<__uint128_t>(a[i]) - b[i] - carry;
|
|
carry = (res >> 127) != static_cast<__uint128_t>(0);
|
|
r[i] = static_cast<uint64_t>(res);
|
|
}
|
|
|
|
return std::make_pair(r, carry);
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr bool BigInt<N>::operator<(const BigInt& b) const {
|
|
return Sub(*this, b).second;
|
|
}
|
|
|
|
template <size_t N>
|
|
std::pair<BigInt<N>, BigInt<N>> BigInt<N>::Div(const BigInt& divisor) const {
|
|
// This is a simple long-division implementation. It is not very efficient and can be improved
|
|
// if this function becomes a bottleneck.
|
|
ASSERT(divisor != BigInt::Zero(), "Divisor must not be zero.");
|
|
|
|
bool carry{};
|
|
BigInt res{};
|
|
BigInt shifted_divisor{}, tmp{};
|
|
BigInt a = *this;
|
|
|
|
while (a >= divisor) {
|
|
tmp = divisor;
|
|
int shift = -1;
|
|
do {
|
|
shifted_divisor = tmp;
|
|
shift++;
|
|
std::tie(tmp, carry) = Add(shifted_divisor, shifted_divisor);
|
|
} while (!carry && tmp <= a);
|
|
|
|
a = Sub(a, shifted_divisor).first;
|
|
res[shift / 64] |= Pow2(shift % 64);
|
|
}
|
|
|
|
return {res, a};
|
|
}
|
|
|
|
template <size_t N>
|
|
std::string BigInt<N>::ToString() const {
|
|
std::ostringstream res;
|
|
res << "0x";
|
|
for (int i = N - 1; i >= 0; --i) {
|
|
res << std::setfill('0') << std::setw(16) << std::hex << (*this)[i];
|
|
}
|
|
return res.str();
|
|
}
|
|
|
|
template <size_t N>
|
|
std::vector<bool> BigInt<N>::ToBoolVector() const {
|
|
std::vector<bool> res;
|
|
for (uint64_t value : value_) {
|
|
for (int i = 0; i < std::numeric_limits<uint64_t>::digits; ++i) {
|
|
res.push_back((value & 1) != 0);
|
|
value >>= 1;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr bool BigInt<N>::operator==(const BigInt<N>& other) const {
|
|
for (size_t i = 0; i < N; ++i) {
|
|
if (gsl::at(value_, i) != gsl::at(other.value_, i)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr BigInt<N> BigInt<N>::ReduceIfNeeded(const BigInt<N>& x, const BigInt<N>& target) {
|
|
ASSERT(target.NumLeadingZeros() > 0, "target must have at least one leading zero.");
|
|
return (x >= target) ? x - target : x;
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr BigInt<N> BigInt<N>::MontMul(
|
|
const BigInt& x, const BigInt& y, const BigInt& modulus, uint64_t montgomery_mprime) {
|
|
BigInt<N> res{};
|
|
ASSERT(modulus.NumLeadingZeros() > 0, "We require at least one leading zero in the modulus");
|
|
ASSERT(y < modulus, "y is supposed to be smaller then the modulus");
|
|
ASSERT(x < modulus, "x is supposed to be smaller then the modulus.");
|
|
for (size_t i = 0; i < N; ++i) {
|
|
__uint128_t temp = Umul128(x[i], y[0]) + res[0];
|
|
uint64_t u_i = gsl::narrow_cast<uint64_t>(temp) * montgomery_mprime;
|
|
uint64_t carry1 = 0, carry2 = 0;
|
|
|
|
for (size_t j = 0; j < N; ++j) {
|
|
if (j != 0) {
|
|
temp = Umul128(x[i], y[j]) + res[j];
|
|
}
|
|
uint64_t low = carry1 + gsl::narrow_cast<uint64_t>(temp);
|
|
carry1 = gsl::narrow_cast<uint64_t>(temp >> 64) + static_cast<uint64_t>(low < carry1);
|
|
temp = Umul128(modulus[j], u_i) + carry2;
|
|
res[j] = low + gsl::narrow_cast<uint64_t>(temp);
|
|
carry2 = gsl::narrow_cast<uint64_t>(temp >> 64) + static_cast<uint64_t>(res[j] < low);
|
|
}
|
|
for (size_t j = 0; j < N - 1; ++j) {
|
|
res[j] = res[j + 1];
|
|
}
|
|
res[N - 1] = carry1 + carry2;
|
|
ASSERT(res[N - 1] >= carry1, "There shouldn't be a carry here.");
|
|
}
|
|
return ReduceIfNeeded(res, modulus);
|
|
}
|
|
|
|
template <size_t N>
|
|
constexpr size_t BigInt<N>::NumLeadingZeros() const {
|
|
int i = value_.size() - 1;
|
|
size_t res = 0;
|
|
|
|
while (i >= 0 && (gsl::at(value_, i) == 0)) {
|
|
i--;
|
|
res += std::numeric_limits<uint64_t>::digits;
|
|
}
|
|
|
|
if (i >= 0) {
|
|
res += __builtin_clzll(gsl::at(value_, i));
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
template <size_t N>
|
|
std::ostream& operator<<(std::ostream& os, const BigInt<N>& bigint) {
|
|
return os << bigint.ToString();
|
|
}
|
|
|
|
namespace bigint {
|
|
namespace details {
|
|
/*
|
|
Converts an hex digit ASCII char to the corresponding int.
|
|
Assumes the input is an hex digit.
|
|
*/
|
|
inline constexpr uint64_t HexCharToUint64(char c) {
|
|
if ('0' <= c && c <= '9') {
|
|
return c - '0';
|
|
}
|
|
|
|
if ('A' <= c && c <= 'F') {
|
|
return c - 'A' + 10;
|
|
}
|
|
|
|
// The function assumes that the input is an hex digit, so we can assume 'a'
|
|
// <= c && c <= 'f' here.
|
|
return c - 'a' + 10;
|
|
}
|
|
|
|
template <char... Chars>
|
|
constexpr auto HexCharArrayToBigInt() {
|
|
constexpr size_t kLen = sizeof...(Chars);
|
|
constexpr std::array<char, kLen> kDigits{Chars...};
|
|
static_assert(kDigits[0] == '0' && kDigits[1] == 'x', "Only hex input is currently supported");
|
|
|
|
constexpr size_t kNibblesPerUint64 = 2 * sizeof(uint64_t);
|
|
constexpr size_t kResLen = (kLen - 2 + kNibblesPerUint64 - 1) / (kNibblesPerUint64);
|
|
std::array<uint64_t, kResLen> res{};
|
|
|
|
for (size_t i = 0; i < kDigits.size() - 2; ++i) {
|
|
const size_t limb = i / kNibblesPerUint64;
|
|
const size_t nibble_offset = i % kNibblesPerUint64;
|
|
const uint64_t nibble = HexCharToUint64(gsl::at(kDigits, kDigits.size() - i - 1));
|
|
|
|
gsl::at(res, limb) |= nibble << (4 * nibble_offset);
|
|
}
|
|
|
|
return BigInt<res.size()>(res);
|
|
}
|
|
} // namespace details
|
|
} // namespace bigint
|
|
|
|
template <char... Chars>
|
|
static constexpr auto operator"" _Z() {
|
|
// This function is implemented as wrapper that calls the actual
|
|
// implementation and stores it in a constexpr variable as we want to force
|
|
// the evaluation to be done in compile time. We need to have the function
|
|
// call because "constexpr auto kRes = BigInt<res.size()>(res);" won't work
|
|
// unless res is constexpr.
|
|
|
|
// Note that the compiler allows HEX and decimal literals but in any case
|
|
// it enforces that Chars... contains only HEX (or decimal) characters.
|
|
constexpr auto kRes = bigint::details::HexCharArrayToBigInt<Chars...>();
|
|
return kRes;
|
|
}
|
|
|
|
} // namespace starkware
|