LibCrypto+LibJS: Move Power to method of {Unsigned,Signed}BigInteger

Having it as a method instead of a free function is necessary for the
next commits and generally allows for optimizations that require deeper
access into the `UnsignedBigInteger` / `SignedBigInteger`.

Also restrict the exponent to 32 bits to avoid huge memory allocations.
This commit is contained in:
devgianlu 2025-04-26 11:17:32 +02:00 committed by Jelle Raaijmakers
commit 5a4cfd05d0
Notes: github-actions[bot] 2025-05-23 09:58:36 +00:00
7 changed files with 53 additions and 46 deletions

View file

@ -5,12 +5,11 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "BigFraction.h"
#include <AK/ByteString.h>
#include <AK/Math.h>
#include <AK/StringBuilder.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibCrypto/NumberTheory/ModularFunctions.h>
namespace Crypto {
@ -36,12 +35,11 @@ ErrorOr<BigFraction> BigFraction::from_string(StringView sv)
auto integer_part = TRY(SignedBigInteger::from_base(10, integer_part_view));
auto fractional_part = TRY(SignedBigInteger::from_base(10, fraction_part_view));
auto fraction_length = UnsignedBigInteger(static_cast<u64>(fraction_part_view.length()));
if (!sv.is_empty() && sv[0] == '-')
fractional_part.negate();
return BigFraction(move(integer_part)) + BigFraction(move(fractional_part), NumberTheory::Power("10"_bigint, move(fraction_length)));
return BigFraction(move(integer_part)) + BigFraction(move(fractional_part), "10"_bigint.pow(fraction_part_view.length()));
}
BigFraction BigFraction::operator+(BigFraction const& rhs) const
@ -129,7 +127,7 @@ BigFraction::BigFraction(double d)
d -= digit * AK::pow(10.0, (double)current_pow);
if (current_pow < 0) {
++decimal_places;
m_denominator.set_to(NumberTheory::Power("10"_bigint, UnsignedBigInteger { decimal_places }));
m_denominator.set_to("10"_bigint.pow(decimal_places));
}
current_pow -= 1;
}
@ -205,7 +203,7 @@ BigFraction BigFraction::rounded(unsigned rounding_threshold) const
auto res = m_numerator.divided_by(m_denominator);
BigFraction result { move(res.quotient) };
auto const needed_power = NumberTheory::Power("10"_bigint, UnsignedBigInteger { rounding_threshold });
auto const needed_power = "10"_bigint.pow(rounding_threshold);
// We get one more digit to do proper rounding
auto const fractional_value = res.remainder.multiplied_by(needed_power.multiplied_by("10"_bigint)).divided_by(m_denominator).quotient;

View file

@ -314,6 +314,26 @@ FLATTEN SignedDivisionResult SignedBigInteger::divided_by(SignedBigInteger const
};
}
FLATTEN SignedBigInteger SignedBigInteger::pow(u32 exponent) const
{
UnsignedBigInteger ep { exponent };
SignedBigInteger base { *this };
SignedBigInteger exp { 1 };
while (!(ep < 1)) {
if (ep.words()[0] % 2 == 1)
exp.set_to(exp.multiplied_by(base));
// ep = ep / 2;
ep.set_to(ep.shift_right(1));
// base = base * base
base.set_to(base.multiplied_by(base));
}
return exp;
}
FLATTEN SignedBigInteger SignedBigInteger::negated_value() const
{
auto result { *this };

View file

@ -106,6 +106,7 @@ public:
[[nodiscard]] SignedBigInteger shift_right(size_t num_bits) const;
[[nodiscard]] SignedBigInteger multiplied_by(SignedBigInteger const& other) const;
[[nodiscard]] SignedDivisionResult divided_by(SignedBigInteger const& divisor) const;
[[nodiscard]] SignedBigInteger pow(u32 exponent) const;
[[nodiscard]] ErrorOr<SignedBigInteger> mod_power_of_two(size_t power_of_two) const;
[[nodiscard]] ErrorOr<SignedBigInteger> try_shift_left(size_t num_bits) const;

View file

@ -547,6 +547,26 @@ FLATTEN UnsignedDivisionResult UnsignedBigInteger::divided_by(UnsignedBigInteger
return UnsignedDivisionResult { quotient, remainder };
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::pow(u32 exponent) const
{
UnsignedBigInteger ep { exponent };
UnsignedBigInteger base { *this };
UnsignedBigInteger exp { 1 };
while (!(ep < 1 )) {
if (ep.words()[0] % 2 == 1)
exp.set_to(exp.multiplied_by(base));
// ep = ep / 2;
ep.set_to(ep.shift_right(1));
// base = base * base
base.set_to(base.multiplied_by(base));
}
return exp;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::gcd(UnsignedBigInteger const& other) const
{
UnsignedBigInteger temp_a { *this };

View file

@ -109,6 +109,7 @@ public:
[[nodiscard]] UnsignedBigInteger as_n_bits(size_t n) const;
[[nodiscard]] UnsignedBigInteger multiplied_by(UnsignedBigInteger const& other) const;
[[nodiscard]] UnsignedDivisionResult divided_by(UnsignedBigInteger const& divisor) const;
[[nodiscard]] UnsignedBigInteger pow(u32 exponent) const;
[[nodiscard]] UnsignedBigInteger gcd(UnsignedBigInteger const& other) const;
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_bitwise_not_fill_to_one_based_index(size_t) const;

View file

@ -1,37 +0,0 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
namespace Crypto::NumberTheory {
// Note: This function _will_ generate extremely huge numbers, and in doing so,
// it will allocate and free a lot of memory!
// Please use |ModularPower| if your use-case is modexp.
template<typename IntegerType>
static IntegerType Power(IntegerType const& b, IntegerType const& e)
{
IntegerType ep { e };
IntegerType base { b };
IntegerType exp { 1 };
while (!(ep < IntegerType { 1 })) {
if (ep.words()[0] % 2 == 1)
exp.set_to(exp.multiplied_by(base));
// ep = ep / 2;
ep.set_to(ep.shift_right(1));
// base = base * base
base.set_to(base.multiplied_by(base));
}
return exp;
}
}

View file

@ -15,7 +15,6 @@
#include <AK/StringFloatingPointConversions.h>
#include <AK/Utf8View.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibCrypto/NumberTheory/ModularFunctions.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Accessor.h>
#include <LibJS/Runtime/Array.h>
@ -1599,7 +1598,7 @@ ThrowCompletionOr<Value> left_shift(VM& vm, Value lhs, Value rhs)
return vm.throw_completion<RangeError>(ErrorType::BigIntSizeExceeded);
// 6.1.6.2.9 BigInt::leftShift ( x, y ), https://tc39.es/ecma262/#sec-numeric-types-bigint-leftShift
auto multiplier_divisor = Crypto::SignedBigInteger { Crypto::NumberTheory::Power(Crypto::UnsignedBigInteger(2), rhs_bigint) };
auto multiplier_divisor = Crypto::SignedBigInteger { Crypto::UnsignedBigInteger(2).pow(rhs_bigint.to_u64()) };
// 1. If y < 0, then
if (rhs_numeric.as_bigint().big_integer().is_negative()) {
@ -2075,9 +2074,14 @@ ThrowCompletionOr<Value> exp(VM& vm, Value lhs, Value rhs)
// 1. If exponent < 0, throw a RangeError exception.
if (exponent.is_negative())
return vm.throw_completion<RangeError>(ErrorType::NegativeExponent);
// AD-HOC: Prevent allocating huge amounts of memory.
if (exponent.unsigned_value().byte_length() > sizeof(u32))
return vm.throw_completion<RangeError>(ErrorType::BigIntSizeExceeded);
// 2. If base is 0 and exponent is 0, return 1.
// 3. Return the BigInt value that represents (base) raised to the power (exponent).
return BigInt::create(vm, Crypto::NumberTheory::Power(base, exponent));
return BigInt::create(vm, base.pow(exponent.to_u64()));
}
return vm.throw_completion<TypeError>(ErrorType::BigIntBadOperatorOtherType, "exponentiation");
}