1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 05:27:14 +09:00

LibCrypto: Replace {Unsigned,Signed}BigInteger impl with LibTomMath

Replace the implementation of maths in `UnsignedBigInteger`
and `SignedBigInteger` with LibTomMath. This gives benefits in terms of
less code to maintain, correctness and speed.

These changes also remove now-unsued methods and improve the error
propagation for functions allocating lots of memory. Additionally, the
new implementation is always trimmed and won't have dangling zeros when
exporting it.
This commit is contained in:
devgianlu 2025-04-25 20:54:45 +02:00 committed by Jelle Raaijmakers
parent 915c6fdcf8
commit 4b3715ccba
Notes: github-actions[bot] 2025-05-23 09:58:22 +00:00
25 changed files with 556 additions and 1973 deletions

View file

@ -302,7 +302,7 @@ ErrorOr<void> Encoder::write_arbitrary_sized_integer(UnsignedBigInteger const& v
auto kind = kind_override.value_or(Kind::Integer);
TRY(write_tag(class_, type, kind));
auto max_byte_size = max(1ull, value.length() * UnsignedBigInteger::BITS_IN_WORD / 8); // At minimum, we need one byte to encode 0.
auto max_byte_size = max(1ull, value.byte_length()); // At minimum, we need one byte to encode 0.
ByteBuffer buffer;
auto output = TRY(buffer.get_bytes_for_writing(max_byte_size));
auto size = value.export_data(output);

View file

@ -157,14 +157,14 @@ double BigFraction::to_double() const
// NOTE: the precision of the result will be 63 bits (more than 53 bits necessary for the mantissa of a double).
if (top_bit_numerator < (top_bit_denominator + 64)) {
shift_left_numerator = top_bit_denominator + 64 - top_bit_numerator;
numerator = numerator.shift_left(shift_left_numerator); // copy
numerator = MUST(numerator.shift_left(shift_left_numerator)); // copy
}
// NOTE: Do nothing if numerator already has more than 64 bits more than denominator.
// 2. Divide [potentially shifted] numerator by the denominator.
auto division_result = numerator.divided_by(denominator);
if (!division_result.remainder.is_zero()) {
division_result.quotient = division_result.quotient.shift_left(1).plus(1); // Extend the quotient with a "fake 1".
division_result.quotient = MUST(division_result.quotient.shift_left(1)).plus(1); // Extend the quotient with a "fake 1".
// NOTE: Since the quotient has at least 63 bits, this will only affect the mantissa
// on rounding, and have the same effect on rounding as any fractional digits (from the remainder).
shift_left_numerator++;

View file

@ -1,277 +0,0 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2025, Manuel Zahariev <manuel@duck.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
#include <AK/NumericLimits.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
namespace Crypto {
/**
* Complexity: O(N) where N is the number of words in the shorter value
* Method:
* Apply <op> word-wise until words in the shorter value are used up
* then copy the rest of the words verbatim from the longer value.
*/
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_or_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& output)
{
UnsignedBigInteger const *shorter, *longer;
if (left.length() < right.length()) {
shorter = &left;
longer = &right;
} else {
shorter = &right;
longer = &left;
}
output.m_words.resize_and_keep_capacity(longer->length());
size_t longer_offset = longer->length() - shorter->length();
for (size_t i = 0; i < shorter->length(); ++i)
output.m_words[i] = longer->words()[i] | shorter->words()[i];
__builtin_memcpy(output.m_words.data() + shorter->length(), longer->words().data() + shorter->length(), sizeof(u32) * longer_offset);
}
/**
* Complexity: O(N) where N is the number of words in the shorter value
* Method:
* Apply 'and' word-wise until words in the shorter value are used up
* and zero the rest.
*/
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_and_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& output)
{
UnsignedBigInteger const *shorter, *longer;
if (left.length() < right.length()) {
shorter = &left;
longer = &right;
} else {
shorter = &right;
longer = &left;
}
output.m_words.resize_and_keep_capacity(longer->length());
size_t longer_offset = longer->length() - shorter->length();
for (size_t i = 0; i < shorter->length(); ++i)
output.m_words[i] = longer->words()[i] & shorter->words()[i];
__builtin_memset(output.m_words.data() + shorter->length(), 0, sizeof(u32) * longer_offset);
}
/**
* Complexity: O(N) where N is the number of words in the shorter value
* Method:
* Apply 'xor' word-wise until words in the shorter value are used up
* and copy the rest.
*/
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& output)
{
UnsignedBigInteger const *shorter, *longer;
if (left.length() < right.length()) {
shorter = &left;
longer = &right;
} else {
shorter = &right;
longer = &left;
}
output.m_words.resize_and_keep_capacity(longer->length());
size_t longer_offset = longer->length() - shorter->length();
for (size_t i = 0; i < shorter->length(); ++i)
output.m_words[i] = longer->words()[i] ^ shorter->words()[i];
__builtin_memcpy(output.m_words.data() + shorter->length(), longer->words().data() + shorter->length(), sizeof(u32) * longer_offset);
}
/**
* Complexity: O(N) where N is the number of words
*/
FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
UnsignedBigInteger const& right,
size_t index,
UnsignedBigInteger& output)
{
if (index == 0) {
output.set_to_0();
return {};
}
size_t size = (index + UnsignedBigInteger::BITS_IN_WORD - 1) / UnsignedBigInteger::BITS_IN_WORD;
TRY(output.m_words.try_resize_and_keep_capacity(size));
VERIFY(size > 0);
for (size_t i = 0; i < size - 1; ++i)
output.m_words[i] = ~(i < right.length() ? right.words()[i] : 0);
index -= (size - 1) * UnsignedBigInteger::BITS_IN_WORD;
auto last_word_index = size - 1;
auto last_word = last_word_index < right.length() ? right.words()[last_word_index] : 0;
output.m_words[last_word_index] = (NumericLimits<UnsignedBigInteger::Word>::max() >> (UnsignedBigInteger::BITS_IN_WORD - index)) & ~last_word;
return {};
}
FLATTEN void UnsignedBigIntegerAlgorithms::shift_left_without_allocation(
UnsignedBigInteger const& number,
size_t num_bits,
UnsignedBigInteger& output)
{
MUST(try_shift_left_without_allocation(number, num_bits, output));
}
/**
* Complexity : O(N) where N is the number of words in the number
*/
FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::try_shift_left_without_allocation(
UnsignedBigInteger const& number,
size_t num_bits,
UnsignedBigInteger& output)
{
size_t const bit_shift = num_bits % UnsignedBigInteger::BITS_IN_WORD;
size_t const bit_shift_complement = UnsignedBigInteger::BITS_IN_WORD - bit_shift;
size_t const zero_based_index_of_highest_set_bit_in_hiword = (number.one_based_index_of_highest_set_bit() - 1) % UnsignedBigInteger::BITS_IN_WORD;
// true if the high word is a result of the bit_shift
bool const hiword_shift = (bit_shift + zero_based_index_of_highest_set_bit_in_hiword) >= UnsignedBigInteger::BITS_IN_WORD;
size_t const word_shift = num_bits / UnsignedBigInteger::BITS_IN_WORD;
TRY(try_shift_left_by_n_words(number, word_shift + (hiword_shift ? 1 : 0), output));
if (bit_shift == 0) // shifting left by an exact number of words)
return {};
UnsignedBigInteger::Word carry = 0;
for (size_t i = 0; i < number.length(); ++i) {
size_t const output_index = i + word_shift;
output.m_words[output_index] = (number.m_words.at(i) << bit_shift) | carry;
carry = (number.m_words.at(i) >> bit_shift_complement);
}
if (hiword_shift)
output.m_words[output.length() - 1] = carry;
return {};
}
/**
* Complexity : O(N) where N is the number of words in the number
*/
FLATTEN void UnsignedBigIntegerAlgorithms::shift_right_without_allocation(
UnsignedBigInteger const& number,
size_t num_bits,
UnsignedBigInteger& output)
{
size_t const bit_shift = num_bits % UnsignedBigInteger::BITS_IN_WORD;
size_t const bit_shift_complement = UnsignedBigInteger::BITS_IN_WORD - bit_shift;
size_t const zero_based_index_of_highest_set_bit_in_hiword = (number.one_based_index_of_highest_set_bit() - 1) % UnsignedBigInteger::BITS_IN_WORD;
// true if the high word will be zeroed as a result of the shift
bool const hiword_zero = (bit_shift > zero_based_index_of_highest_set_bit_in_hiword);
size_t const word_shift = num_bits / UnsignedBigInteger::BITS_IN_WORD + (hiword_zero ? 1 : 0);
if (word_shift >= number.length()) { // all non-zero digits have been shifted right; result is zero
output.set_to_0();
return;
}
shift_right_by_n_words(number, word_shift, output);
if (bit_shift == 0) // shifting right by an exact number of words)
return;
size_t const output_length = output.length();
size_t number_index = number.length() - 1;
UnsignedBigInteger::Word carry = 0;
if (hiword_zero) {
carry = number.words().at(number_index) << bit_shift_complement;
--number_index;
}
for (size_t i = 0; i < output_length; ++i) {
size_t const output_index = output_length - i - 1; // downto index 0
output.m_words[output_index] = ((number.m_words.at(number_index) >> bit_shift)) | carry;
carry = (number.m_words.at(number_index) << bit_shift_complement);
--number_index;
}
}
void UnsignedBigIntegerAlgorithms::shift_left_by_n_words(
UnsignedBigInteger const& number,
size_t number_of_words,
UnsignedBigInteger& output)
{
MUST(try_shift_left_by_n_words(number, number_of_words, output));
}
ErrorOr<void> UnsignedBigIntegerAlgorithms::try_shift_left_by_n_words(
UnsignedBigInteger const& number,
size_t number_of_words,
UnsignedBigInteger& output)
{
// shifting left by N words means just inserting N zeroes to the beginning of the words vector
output.set_to_0();
TRY(output.m_words.try_resize_and_keep_capacity(number_of_words + number.length()));
__builtin_memset(output.m_words.data(), 0, number_of_words * sizeof(unsigned));
__builtin_memcpy(&output.m_words.data()[number_of_words], number.m_words.data(), number.m_words.size() * sizeof(unsigned));
return {};
}
void UnsignedBigIntegerAlgorithms::shift_right_by_n_words(
UnsignedBigInteger const& number,
size_t number_of_words,
UnsignedBigInteger& output)
{
// shifting right by N words means just not copying the first words
output.set_to_0();
output.m_words.resize_and_keep_capacity(number.length() - number_of_words);
__builtin_memcpy(output.m_words.data(), &number.m_words.data()[number_of_words], (number.m_words.size() - number_of_words) * sizeof(unsigned));
}
/**
* Returns the word at a requested index in the result of a shift operation
*/
ALWAYS_INLINE UnsignedBigInteger::Word UnsignedBigIntegerAlgorithms::shift_left_get_one_word(
UnsignedBigInteger const& number,
size_t num_bits,
size_t result_word_index)
{
// "<= length()" (rather than length() - 1) is intentional,
// The result index of length() is used when calculating the carry word
VERIFY(result_word_index <= number.length());
VERIFY(num_bits <= UnsignedBigInteger::BITS_IN_WORD);
u32 result = 0;
// we need to check for "num_bits != 0" since shifting right by 32 is apparently undefined behavior!
if (result_word_index > 0 && num_bits != 0) {
result += number.m_words[result_word_index - 1] >> (UnsignedBigInteger::BITS_IN_WORD - num_bits);
}
if (result_word_index < number.length() && num_bits < 32) {
result += number.m_words[result_word_index] << num_bits;
}
return result;
}
}

View file

@ -1,122 +0,0 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
#include <AK/BigIntBase.h>
namespace Crypto {
using AK::Detail::div_mod_words;
using AK::Detail::dword;
/**
* Complexity: O(N^2) where N is the number of words in the larger number
* Division method:
* Knuth's Algorithm D, see UFixedBigIntDivision.h for more details
*/
FLATTEN void UnsignedBigIntegerAlgorithms::divide_without_allocation(
UnsignedBigInteger const& numerator,
UnsignedBigInteger const& denominator,
UnsignedBigInteger& quotient,
UnsignedBigInteger& remainder)
{
size_t dividend_len = numerator.trimmed_length();
size_t divisor_len = denominator.trimmed_length();
VERIFY(divisor_len != 0);
// Fast paths
// Division by 1
if (divisor_len == 1 && denominator.m_words[0] == 1) {
quotient.set_to(numerator);
remainder.set_to_0();
return;
}
if (dividend_len < divisor_len) {
quotient.set_to_0();
remainder.set_to(numerator);
return;
}
if (divisor_len == 1 && dividend_len == 1) {
quotient.set_to(numerator.m_words[0] / denominator.m_words[0]);
remainder.set_to(numerator.m_words[0] % denominator.m_words[0]);
return;
}
// Division by Word
if (divisor_len == 1) {
quotient.resize_with_leading_zeros(dividend_len);
remainder.resize_with_leading_zeros(1);
// FIXME: Use a "DoubleWord" to allow increasing the Word size of
// BigInt in the future
static_assert(UnsignedBigInteger::BITS_IN_WORD == 32);
auto u = dword(numerator.m_words[dividend_len - 2], numerator.m_words[dividend_len - 1]);
auto divisor = denominator.m_words[0];
auto top = u / divisor;
quotient.m_words[dividend_len - 1] = top >> UnsignedBigInteger::BITS_IN_WORD;
quotient.m_words[dividend_len - 2] = static_cast<UnsignedBigInteger::Word>(top);
auto carry = static_cast<UnsignedBigInteger::Word>(u % divisor);
for (size_t i = dividend_len - 2; i-- != 0;)
quotient.m_words[i] = div_mod_words(numerator.m_words[i], carry, divisor, carry);
remainder.m_words[0] = carry;
return;
}
// Knuth's algorithm D
auto dividend = numerator;
dividend.resize_with_leading_zeros(dividend_len + 1);
auto divisor = denominator;
quotient.resize_with_leading_zeros(dividend_len - divisor_len + 1);
remainder.resize_with_leading_zeros(divisor_len);
Ops::div_mod_internal<true>(
dividend.words_span(), divisor.words_span(),
quotient.words_span(), remainder.words_span(),
dividend_len, divisor_len);
}
/**
* Complexity : O(N) where N is the number of digits in the numerator
* Division method :
* Starting from the most significant one, for each half-word of the numerator, combine it
* with the existing remainder if any, divide the combined number as a u32 operation and
* update the quotient / remainder as needed.
*/
FLATTEN void UnsignedBigIntegerAlgorithms::divide_u16_without_allocation(
UnsignedBigInteger const& numerator,
UnsignedBigInteger::Word denominator,
UnsignedBigInteger& quotient,
UnsignedBigInteger& remainder)
{
VERIFY(denominator < (1 << 16));
UnsignedBigInteger::Word remainder_word = 0;
auto numerator_length = numerator.trimmed_length();
quotient.set_to_0();
quotient.m_words.resize(numerator_length);
for (int word_index = numerator_length - 1; word_index >= 0; --word_index) {
auto word_high = numerator.m_words[word_index] >> 16;
auto word_low = numerator.m_words[word_index] & ((1 << 16) - 1);
auto number_to_divide_high = (remainder_word << 16) | word_high;
auto quotient_high = number_to_divide_high / denominator;
remainder_word = number_to_divide_high % denominator;
auto number_to_divide_low = remainder_word << 16 | word_low;
auto quotient_low = number_to_divide_low / denominator;
remainder_word = number_to_divide_low % denominator;
quotient.m_words[word_index] = (quotient_high << 16) | quotient_low;
}
remainder.set_to(remainder_word);
}
}

View file

@ -1,94 +0,0 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
namespace Crypto {
void UnsignedBigIntegerAlgorithms::destructive_GCD_without_allocation(
UnsignedBigInteger& temp_a,
UnsignedBigInteger& temp_b,
UnsignedBigInteger& temp_quotient,
UnsignedBigInteger& temp_remainder,
UnsignedBigInteger& output)
{
for (;;) {
if (temp_a == 0) {
output.set_to(temp_b);
return;
}
// temp_b %= temp_a
divide_without_allocation(temp_b, temp_a, temp_quotient, temp_remainder);
temp_b.set_to(temp_remainder);
if (temp_b == 0) {
output.set_to(temp_a);
return;
}
// temp_a %= temp_b
divide_without_allocation(temp_a, temp_b, temp_quotient, temp_remainder);
temp_a.set_to(temp_remainder);
}
}
void UnsignedBigIntegerAlgorithms::extended_GCD_without_allocation(
UnsignedBigInteger const& a,
UnsignedBigInteger const& b,
UnsignedBigInteger& x,
UnsignedBigInteger& y,
UnsignedBigInteger& gcd,
UnsignedBigInteger& temp_quotient,
UnsignedBigInteger& temp_1,
UnsignedBigInteger& temp_2,
UnsignedBigInteger& temp_shift,
UnsignedBigInteger& temp_r,
UnsignedBigInteger& temp_s,
UnsignedBigInteger& temp_t)
{
gcd.set_to(a);
x.set_to(1);
y.set_to(0);
temp_r.set_to(b);
temp_s.set_to_0();
temp_t.set_to(1);
while (temp_r != 0) {
// quotient := old_r div r
divide_without_allocation(gcd, temp_r, temp_quotient, temp_1);
temp_2.set_to(temp_r);
multiply_without_allocation(temp_quotient, temp_r, temp_shift, temp_1);
while (gcd < temp_1) {
add_into_accumulator_without_allocation(gcd, b);
}
MUST(subtract_without_allocation(gcd, temp_1, temp_r));
gcd.set_to(temp_2);
// (old_s, s) := (s, old_s quotient × s)
temp_2.set_to(temp_s);
multiply_without_allocation(temp_quotient, temp_s, temp_shift, temp_1);
while (x < temp_1) {
add_into_accumulator_without_allocation(x, b);
}
MUST(subtract_without_allocation(x, temp_1, temp_s));
x.set_to(temp_2);
// (old_t, t) := (t, old_t quotient × t)
temp_2.set_to(temp_t);
multiply_without_allocation(temp_quotient, temp_t, temp_shift, temp_1);
while (y < temp_1) {
add_into_accumulator_without_allocation(y, b);
}
MUST(subtract_without_allocation(y, temp_1, temp_t));
y.set_to(temp_2);
}
}
}

View file

@ -1,34 +0,0 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
namespace Crypto {
void UnsignedBigIntegerAlgorithms::modular_inverse_without_allocation(
UnsignedBigInteger const& a,
UnsignedBigInteger const& b,
UnsignedBigInteger& result,
UnsignedBigInteger& temp_y,
UnsignedBigInteger& temp_gcd,
UnsignedBigInteger& temp_quotient,
UnsignedBigInteger& temp_1,
UnsignedBigInteger& temp_2,
UnsignedBigInteger& temp_shift,
UnsignedBigInteger& temp_r,
UnsignedBigInteger& temp_s,
UnsignedBigInteger& temp_t)
{
extended_GCD_without_allocation(a, b, result, temp_y, temp_gcd, temp_quotient, temp_1, temp_2, temp_shift, temp_r, temp_s, temp_t);
divide_without_allocation(result, b, temp_quotient, temp_1);
add_into_accumulator_without_allocation(temp_1, b);
divide_without_allocation(temp_1, b, temp_quotient, result);
}
}

View file

@ -1,279 +0,0 @@
/*
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
namespace Crypto {
void UnsignedBigIntegerAlgorithms::destructive_modular_power_without_allocation(
UnsignedBigInteger& ep,
UnsignedBigInteger& base,
UnsignedBigInteger const& m,
UnsignedBigInteger& temp_1,
UnsignedBigInteger& temp_multiply,
UnsignedBigInteger& temp_quotient,
UnsignedBigInteger& temp_remainder,
UnsignedBigInteger& exp)
{
exp.set_to(1);
while (!(ep < 1)) {
if (ep.words()[0] % 2 == 1) {
// exp = (exp * base) % m;
multiply_without_allocation(exp, base, temp_1, temp_multiply);
divide_without_allocation(temp_multiply, m, temp_quotient, temp_remainder);
exp.set_to(temp_remainder);
}
// ep = ep / 2;
ep.set_to(ep.shift_right(1));
// base = (base * base) % m;
multiply_without_allocation(base, base, temp_1, temp_multiply);
divide_without_allocation(temp_multiply, m, temp_quotient, temp_remainder);
base.set_to(temp_remainder);
// Note that not clamping here would cause future calculations (multiply, specifically) to allocate even more unused space
// which would then persist through the temp bigints, and significantly slow down later loops.
// To avoid that, we can clamp to a specific max size, or just clamp to the min needed amount of space.
ep.clamp_to_trimmed_length();
exp.clamp_to_trimmed_length();
base.clamp_to_trimmed_length();
}
}
/**
* Compute (1/value) % 2^32.
* This needs an odd input value
* Algorithm from: Dumas, J.G. "On NewtonRaphson Iteration for Multiplicative Inverses Modulo Prime Powers".
*/
ALWAYS_INLINE static u32 inverse_wrapped(u32 value)
{
VERIFY(value & 1);
u64 b = static_cast<u64>(value);
u64 k0 = (2 - b);
u64 t = (b - 1);
size_t i = 1;
while (i < 32) {
t = t * t;
k0 = k0 * (t + 1);
i <<= 1;
}
return static_cast<u32>(-k0);
}
/**
* Computes z = x * y + c. z_carry contains the top bits, z contains the bottom bits.
*/
ALWAYS_INLINE static void linear_multiplication_with_carry(u32 x, u32 y, u32 c, u32& z_carry, u32& z)
{
u64 result = static_cast<u64>(x) * static_cast<u64>(y) + static_cast<u64>(c);
z_carry = static_cast<u32>(result >> 32);
z = static_cast<u32>(result);
}
/**
* Computes z = a + b. z_carry contains the top bit (1 or 0), z contains the bottom bits.
*/
ALWAYS_INLINE static void addition_with_carry(u32 a, u32 b, u32& z_carry, u32& z)
{
u64 result = static_cast<u64>(a) + static_cast<u64>(b);
z_carry = static_cast<u32>(result >> 32);
z = static_cast<u32>(result);
}
/**
* Computes a montgomery "fragment" for y_i. This computes "z[i] += x[i] * y_i" for all words while rippling the carry, and returns the carry.
* Algorithm from: Gueron, "Efficient Software Implementations of Modular Exponentiation". (https://eprint.iacr.org/2011/239.pdf)
*/
UnsignedBigInteger::Word UnsignedBigIntegerAlgorithms::montgomery_fragment(UnsignedBigInteger& z, size_t offset_in_z, UnsignedBigInteger const& x, UnsignedBigInteger::Word y_digit, size_t num_words)
{
UnsignedBigInteger::Word carry { 0 };
for (size_t i = 0; i < num_words; ++i) {
UnsignedBigInteger::Word a_carry;
UnsignedBigInteger::Word a;
linear_multiplication_with_carry(x.m_words[i], y_digit, z.m_words[offset_in_z + i], a_carry, a);
UnsignedBigInteger::Word b_carry;
UnsignedBigInteger::Word b;
addition_with_carry(a, carry, b_carry, b);
z.m_words[offset_in_z + i] = b;
carry = a_carry + b_carry;
}
return carry;
}
/**
* Computes the "almost montgomery" product : x * y * 2 ^ (-num_words * BITS_IN_WORD) % modulo
* [Note : that means that the result z satisfies z * 2^(num_words * BITS_IN_WORD) % modulo = x * y % modulo]
* assuming :
* - x, y and modulo are all already padded to num_words
* - k = inverse_wrapped(modulo) (optimization to not recompute K each time)
* Algorithm from: Gueron, "Efficient Software Implementations of Modular Exponentiation". (https://eprint.iacr.org/2011/239.pdf)
*/
void UnsignedBigIntegerAlgorithms::almost_montgomery_multiplication_without_allocation(
UnsignedBigInteger const& x,
UnsignedBigInteger const& y,
UnsignedBigInteger const& modulo,
UnsignedBigInteger& z,
UnsignedBigInteger::Word k,
size_t num_words,
UnsignedBigInteger& result)
{
VERIFY(x.length() >= num_words);
VERIFY(y.length() >= num_words);
VERIFY(modulo.length() >= num_words);
z.set_to(0);
z.resize_with_leading_zeros(num_words * 2);
UnsignedBigInteger::Word previous_double_carry { 0 };
for (size_t i = 0; i < num_words; ++i) {
// z[i->num_words+i] += x * y_i
UnsignedBigInteger::Word carry_1 = montgomery_fragment(z, i, x, y.m_words[i], num_words);
// z[i->num_words+i] += modulo * (z_i * k)
UnsignedBigInteger::Word t = z.m_words[i] * k;
UnsignedBigInteger::Word carry_2 = montgomery_fragment(z, i, modulo, t, num_words);
// Compute the carry by combining all of the carries of the previous computations
// Put it "right after" the range that we computed above
UnsignedBigInteger::Word temp_carry = previous_double_carry + carry_1;
UnsignedBigInteger::Word overall_carry = temp_carry + carry_2;
z.m_words[num_words + i] = overall_carry;
// Detect if there was a "double carry" for this word by checking if our carry results are smaller than their components
previous_double_carry = (temp_carry < carry_1 || overall_carry < carry_2) ? 1 : 0;
}
if (previous_double_carry == 0) {
// Return the top num_words bytes of Z, which contains our result.
shift_right_by_n_words(z, num_words, result);
result.resize_with_leading_zeros(num_words);
return;
}
// We have a carry, so we're "one bigger" than we need to be.
// Subtract the modulo from the result (the top half of z), and write it to the bottom half of Z since we have space.
// (With carry, of course.)
UnsignedBigInteger::Word c { 0 };
for (size_t i = 0; i < num_words; ++i) {
UnsignedBigInteger::Word z_digit = z.m_words[num_words + i];
UnsignedBigInteger::Word modulo_digit = modulo.m_words[i];
UnsignedBigInteger::Word new_z_digit = z_digit - modulo_digit - c;
z.m_words[i] = new_z_digit;
// Detect if the subtraction underflowed - from "Hacker's Delight"
c = ((modulo_digit & ~z_digit) | ((modulo_digit | ~z_digit) & new_z_digit)) >> (UnsignedBigInteger::BITS_IN_WORD - 1);
}
// Return the bottom num_words bytes of Z (with the carry bit handled)
z.m_words.resize(num_words);
result.set_to(z);
result.resize_with_leading_zeros(num_words);
}
/**
* Complexity: still O(N^3) with N the number of words in the largest word, but less complex than the classical mod power.
* Note: the montgomery multiplications requires an inverse modulo over 2^32, which is only defined for odd numbers.
*/
void UnsignedBigIntegerAlgorithms::montgomery_modular_power_with_minimal_allocations(
UnsignedBigInteger const& base,
UnsignedBigInteger const& exponent,
UnsignedBigInteger const& modulo,
UnsignedBigInteger& temp_z,
UnsignedBigInteger& rr,
UnsignedBigInteger& one,
UnsignedBigInteger& z,
UnsignedBigInteger& zz,
UnsignedBigInteger& x,
UnsignedBigInteger& temp_extra,
UnsignedBigInteger& result)
{
VERIFY(modulo.is_odd());
// Note: While this is a constexpr variable for clarity and could be changed in theory,
// various optimized parts of the algorithm rely on this value being exactly 4.
constexpr size_t window_size = 4;
size_t num_words = modulo.trimmed_length();
UnsignedBigInteger::Word k = inverse_wrapped(modulo.m_words[0]);
one.set_to(1);
// rr = ( 2 ^ (2 * modulo.length() * BITS_IN_WORD) ) % modulo
shift_left_by_n_words(one, 2 * num_words, x);
divide_without_allocation(x, modulo, temp_extra, rr);
rr.resize_with_leading_zeros(num_words);
// x = base [% modulo, if x doesn't already fit in modulo's words]
x.set_to(base);
if (x.trimmed_length() > num_words)
divide_without_allocation(base, modulo, temp_extra, x);
x.resize_with_leading_zeros(num_words);
one.set_to(1);
one.resize_with_leading_zeros(num_words);
// Compute the montgomery powers from 0 to 2^window_size. powers[i] = x^i
UnsignedBigInteger powers[1 << window_size];
almost_montgomery_multiplication_without_allocation(one, rr, modulo, temp_z, k, num_words, powers[0]);
almost_montgomery_multiplication_without_allocation(x, rr, modulo, temp_z, k, num_words, powers[1]);
for (size_t i = 2; i < (1 << window_size); ++i)
almost_montgomery_multiplication_without_allocation(powers[i - 1], powers[1], modulo, temp_z, k, num_words, powers[i]);
z.set_to(powers[0]);
z.resize_with_leading_zeros(num_words);
zz.set_to(0);
zz.resize_with_leading_zeros(num_words);
ssize_t exponent_length = exponent.trimmed_length();
for (ssize_t word_in_exponent = exponent_length - 1; word_in_exponent >= 0; --word_in_exponent) {
UnsignedBigInteger::Word exponent_word = exponent.m_words[word_in_exponent];
size_t bit_in_word = 0;
while (bit_in_word < UnsignedBigInteger::BITS_IN_WORD) {
if (word_in_exponent != exponent_length - 1 || bit_in_word != 0) {
almost_montgomery_multiplication_without_allocation(z, z, modulo, temp_z, k, num_words, zz);
almost_montgomery_multiplication_without_allocation(zz, zz, modulo, temp_z, k, num_words, z);
almost_montgomery_multiplication_without_allocation(z, z, modulo, temp_z, k, num_words, zz);
almost_montgomery_multiplication_without_allocation(zz, zz, modulo, temp_z, k, num_words, z);
}
auto power_index = exponent_word >> (UnsignedBigInteger::BITS_IN_WORD - window_size);
auto& power = powers[power_index];
almost_montgomery_multiplication_without_allocation(z, power, modulo, temp_z, k, num_words, zz);
swap(z, zz);
// Move to the next window
exponent_word <<= window_size;
bit_in_word += window_size;
}
}
almost_montgomery_multiplication_without_allocation(z, one, modulo, temp_z, k, num_words, zz);
if (zz < modulo) {
result.set_to(zz);
result.clamp_to_trimmed_length();
return;
}
// Note : Since we were using "almost montgomery" multiplications, we aren't guaranteed to be under the modulo already.
// So, if we're here, we need to respect the modulo.
// We can, however, start by trying to subtract the modulo, just in case we're close.
MUST(subtract_without_allocation(zz, modulo, result));
if (modulo < zz) {
// Note: This branch shouldn't happen in theory (as noted in https://github.com/rust-num/num-bigint/blob/master/src/biguint/monty.rs#L210)
// Let's dbgln the values we used. That way, if we hit this branch, we can contribute these values for test cases.
dbgln("Encountered the modulo branch during a montgomery modular power. Params : {} - {} - {}", base, exponent, modulo);
// We just clobber all the other temporaries that we don't need for the division.
// This is wasteful, but we're on the edgiest of cases already.
divide_without_allocation(zz, modulo, temp_extra, result);
}
result.clamp_to_trimmed_length();
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
namespace Crypto {
/**
* Complexity: O(N^2) where N is the number of words in the larger number
* Multiplication method:
* An integer is equal to the sum of the powers of two
* according to the indices of its 'on' bits.
* So to multiple x*y, we go over each '1' bit in x (say the i'th bit),
* and add y<<i to the result.
*/
FLATTEN void UnsignedBigIntegerAlgorithms::multiply_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& temp_shift,
UnsignedBigInteger& output)
{
output.set_to_0();
// iterate all bits
for (size_t word_index = 0; word_index < left.length(); ++word_index) {
for (size_t bit_index = 0; bit_index < UnsignedBigInteger::BITS_IN_WORD; ++bit_index) {
// If the bit is off - skip over it
if (!(left.m_words[word_index] & (1 << bit_index)))
continue;
size_t shift_amount = word_index * UnsignedBigInteger::BITS_IN_WORD + bit_index;
// output += (right << shift_amount);
shift_left_without_allocation(right, shift_amount, temp_shift);
add_into_accumulator_without_allocation(output, temp_shift);
}
}
}
}

View file

@ -1,109 +0,0 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2020-2021, Dex <dexes.ttp@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigIntegerAlgorithms.h"
namespace Crypto {
/**
* Complexity: O(N) where N is the number of words in the larger number
*/
void UnsignedBigIntegerAlgorithms::add_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& output)
{
UnsignedBigInteger const* const longer = (left.length() > right.length()) ? &left : &right;
UnsignedBigInteger const* const shorter = (longer == &right) ? &left : &right;
output.set_to(*longer);
add_into_accumulator_without_allocation(output, *shorter);
}
/**
* Complexity: O(N) where N is the number of words in the larger number
*/
void UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(UnsignedBigInteger& accumulator, UnsignedBigInteger const& value)
{
auto value_length = value.trimmed_length();
// If needed, resize the accumulator so it can fit the value.
accumulator.resize_with_leading_zeros(value_length);
auto final_length = accumulator.length();
// Add the words of the value into the accumulator, rippling any carry as we go
UnsignedBigInteger::Word last_carry_for_word = 0;
for (size_t i = 0; i < value_length; ++i) {
UnsignedBigInteger::Word current_carry_for_word = 0;
if (Checked<UnsignedBigInteger::Word>::addition_would_overflow(value.m_words[i], accumulator.m_words[i])) {
current_carry_for_word = 1;
}
UnsignedBigInteger::Word word_addition_result = value.m_words[i] + accumulator.m_words[i];
if (Checked<UnsignedBigInteger::Word>::addition_would_overflow(word_addition_result, last_carry_for_word)) {
current_carry_for_word = 1;
}
word_addition_result += last_carry_for_word;
last_carry_for_word = current_carry_for_word;
accumulator.m_words[i] = word_addition_result;
}
// Ripple the carry over the remaining words in the accumulator until either there is no carry left or we run out of words
while (last_carry_for_word && final_length > value_length) {
UnsignedBigInteger::Word current_carry_for_word = 0;
if (Checked<UnsignedBigInteger::Word>::addition_would_overflow(accumulator.m_words[value_length], last_carry_for_word)) {
current_carry_for_word = 1;
}
accumulator.m_words[value_length] += last_carry_for_word;
last_carry_for_word = current_carry_for_word;
value_length++;
}
if (last_carry_for_word) {
// Note : The accumulator couldn't add the carry directly, so we reached its end
accumulator.m_words.append(last_carry_for_word);
}
accumulator.m_cached_trimmed_length = {};
}
/**
* Complexity: O(N) where N is the number of words in the larger number
*/
ErrorOr<void> UnsignedBigIntegerAlgorithms::subtract_without_allocation(
UnsignedBigInteger const& left,
UnsignedBigInteger const& right,
UnsignedBigInteger& output)
{
if (left < right) {
return Error::from_string_literal("Invalid subtraction: left < right");
}
u8 borrow = 0;
auto own_length = left.length();
auto other_length = right.length();
output.set_to_0();
output.m_words.resize_and_keep_capacity(own_length);
for (size_t i = 0; i < own_length; ++i) {
u32 other_word = (i < other_length) ? right.m_words[i] : 0;
i64 temp = static_cast<i64>(left.m_words[i]) - static_cast<i64>(other_word) - static_cast<i64>(borrow);
// If temp < 0, we had an underflow
borrow = (temp >= 0) ? 0 : 1;
if (temp < 0) {
temp += (UINT32_MAX + 1);
}
output.m_words[i] = temp;
}
// This assertion should not fail, because we verified that *this>=other at the beginning of the function
VERIFY(borrow == 0);
return {};
}
}

View file

@ -1,49 +0,0 @@
/*
* Copyright (c) 2021, Dex <dexes.ttp@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
namespace Crypto {
class UnsignedBigIntegerAlgorithms {
using Ops = AK::StorageOperations<UnsignedBigInteger::Word>;
public:
static void add_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void add_into_accumulator_without_allocation(UnsignedBigInteger& accumulator, UnsignedBigInteger const& value);
static ErrorOr<void> subtract_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_or_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_and_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void bitwise_xor_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
static void shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& output);
static void shift_right_without_allocation(UnsignedBigInteger const& number, size_t num_bits, UnsignedBigInteger& output);
static void multiply_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& temp_shift, UnsignedBigInteger& output);
static void divide_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger const& denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
static void divide_u16_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger::Word denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
static ErrorOr<void> bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
static ErrorOr<void> try_shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& output);
static void extended_GCD_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& x, UnsignedBigInteger& y, UnsignedBigInteger& gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t);
static void destructive_GCD_without_allocation(UnsignedBigInteger& temp_a, UnsignedBigInteger& temp_b, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& output);
static void modular_inverse_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& result, UnsignedBigInteger& temp_y, UnsignedBigInteger& temp_gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t);
static void destructive_modular_power_without_allocation(UnsignedBigInteger& ep, UnsignedBigInteger& base, UnsignedBigInteger const& m, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_multiply, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_remainder, UnsignedBigInteger& result);
static void montgomery_modular_power_with_minimal_allocations(UnsignedBigInteger const& base, UnsignedBigInteger const& exponent, UnsignedBigInteger const& modulo, UnsignedBigInteger& temp_z0, UnsignedBigInteger& temp_rr, UnsignedBigInteger& temp_one, UnsignedBigInteger& temp_z, UnsignedBigInteger& temp_zz, UnsignedBigInteger& temp_x, UnsignedBigInteger& temp_extra, UnsignedBigInteger& result);
private:
static UnsignedBigInteger::Word montgomery_fragment(UnsignedBigInteger& z, size_t offset_in_z, UnsignedBigInteger const& x, UnsignedBigInteger::Word y_digit, size_t num_words);
static void almost_montgomery_multiplication_without_allocation(UnsignedBigInteger const& x, UnsignedBigInteger const& y, UnsignedBigInteger const& modulo, UnsignedBigInteger& z, UnsignedBigInteger::Word k, size_t num_words, UnsignedBigInteger& result);
static void shift_left_by_n_words(UnsignedBigInteger const& number, size_t number_of_words, UnsignedBigInteger& output);
static void shift_right_by_n_words(UnsignedBigInteger const& number, size_t number_of_words, UnsignedBigInteger& output);
ALWAYS_INLINE static UnsignedBigInteger::Word shift_left_get_one_word(UnsignedBigInteger const& number, size_t num_bits, size_t result_word_index);
static ErrorOr<void> try_shift_left_by_n_words(UnsignedBigInteger const& number, size_t number_of_words, UnsignedBigInteger& output);
};
}

View file

@ -1,243 +1,241 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "SignedBigInteger.h"
#include <AK/StringBuilder.h>
#include <math.h>
#include <tommath.h>
#include <AK/StringBuilder.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibCrypto/BigInt/Tommath.h>
namespace Crypto {
SignedBigInteger::SignedBigInteger(UnsignedBigInteger&& unsigned_data, bool sign)
{
MP_MUST(mp_init_copy(&m_mp, &unsigned_data.m_mp));
if (sign) {
MP_MUST(mp_neg(&m_mp, &m_mp));
}
}
SignedBigInteger::SignedBigInteger(u8 const* ptr, size_t length)
{
MP_MUST(mp_init(&m_mp));
MP_MUST(mp_from_sbin(&m_mp, ptr, length));
}
SignedBigInteger::SignedBigInteger(UnsignedBigInteger const& unsigned_data)
{
MP_MUST(mp_init_copy(&m_mp, &unsigned_data.m_mp));
}
SignedBigInteger::SignedBigInteger(double value)
: m_sign(value < 0.0)
, m_unsigned_data(fabs(value))
{
MP_MUST(mp_init(&m_mp));
MP_MUST(mp_set_double(&m_mp, value));
}
SignedBigInteger SignedBigInteger::import_data(u8 const* ptr, size_t length)
SignedBigInteger::SignedBigInteger(i64 value)
{
bool sign = *ptr;
auto unsigned_data = UnsignedBigInteger::import_data(ptr + 1, length - 1);
return { move(unsigned_data), sign };
MP_MUST(mp_init(&m_mp));
mp_set_i64(&m_mp, value);
}
size_t SignedBigInteger::export_data(Bytes data, bool remove_leading_zeros) const
SignedBigInteger::SignedBigInteger(SignedBigInteger const& other)
{
// FIXME: Support this:
// m <0XX> -> m <XX> (if remove_leading_zeros)
VERIFY(!remove_leading_zeros);
MP_MUST(mp_init_copy(&m_mp, &other.m_mp));
}
data[0] = m_sign;
auto bytes_view = data.slice(1, data.size() - 1);
return m_unsigned_data.export_data(bytes_view, remove_leading_zeros) + 1;
SignedBigInteger& SignedBigInteger::operator=(SignedBigInteger const& other)
{
if (this == &other)
return *this;
mp_clear(&m_mp);
MP_MUST(mp_init_copy(&m_mp, &other.m_mp));
return *this;
}
SignedBigInteger::SignedBigInteger()
{
MP_MUST(mp_init(&m_mp));
}
SignedBigInteger::~SignedBigInteger()
{
mp_clear(&m_mp);
}
size_t SignedBigInteger::export_data(Bytes data) const
{
size_t written = 0;
MP_MUST(mp_to_sbin(&m_mp, data.data(), data.size(), &written));
return written;
}
ErrorOr<SignedBigInteger> SignedBigInteger::from_base(u16 N, StringView str)
{
auto sign = false;
if (str.length() > 1) {
auto maybe_sign = str[0];
if (maybe_sign == '-') {
str = str.substring_view(1);
sign = true;
VERIFY(N <= 36);
if (str.is_empty())
return SignedBigInteger(0);
auto buffer = TRY(ByteBuffer::create_zeroed(str.length() + 1));
size_t idx = 0;
for (auto& c : str) {
if (c == '_') {
// Skip underscores
continue;
}
if (maybe_sign == '+')
str = str.substring_view(1);
buffer[idx++] = c;
}
auto unsigned_data = TRY(UnsignedBigInteger::from_base(N, str));
return SignedBigInteger { move(unsigned_data), sign };
SignedBigInteger result;
if (mp_read_radix(&result.m_mp, reinterpret_cast<char const*>(buffer.data()), N) != MP_OKAY)
return Error::from_string_literal("Invalid number");
return result;
}
ErrorOr<String> SignedBigInteger::to_base(u16 N) const
{
StringBuilder builder;
VERIFY(N <= 36);
if (is_zero())
return "0"_string;
if (m_sign)
TRY(builder.try_append('-'));
int size = 0;
MP_MUST(mp_radix_size(&m_mp, N, &size));
auto buffer = TRY(ByteBuffer::create_zeroed(size));
auto unsigned_as_base = TRY(m_unsigned_data.to_base(N));
TRY(builder.try_append(unsigned_as_base.bytes_as_string_view()));
size_t written = 0;
MP_MUST(mp_to_radix(&m_mp, reinterpret_cast<char*>(buffer.data()), size, &written, N));
return builder.to_string();
return StringView(buffer.bytes().slice(0, written - 1)).to_ascii_lowercase_string();
}
u64 SignedBigInteger::to_u64() const
{
u64 unsigned_value = m_unsigned_data.to_u64();
if (!m_sign)
return unsigned_value;
return ~(unsigned_value - 1); // equivalent to `-unsigned_value`, but doesn't trigger UBSAN
return mp_get_u64(&m_mp);
}
double SignedBigInteger::to_double(UnsignedBigInteger::RoundingMode rounding_mode) const
{
double unsigned_value = m_unsigned_data.to_double(rounding_mode);
if (!m_sign)
return unsigned_value;
int sign = mp_isneg(&m_mp) ? -1 : 1;
return unsigned_value().to_double(rounding_mode) * sign;
}
VERIFY(!is_zero());
return -unsigned_value;
UnsignedBigInteger SignedBigInteger::unsigned_value() const
{
UnsignedBigInteger result;
MP_MUST(mp_abs(&m_mp, &result.m_mp));
return result;
}
bool SignedBigInteger::is_negative() const
{
return mp_isneg(&m_mp);
}
bool SignedBigInteger::is_zero() const
{
return mp_iszero(&m_mp);
}
void SignedBigInteger::negate()
{
MP_MUST(mp_neg(&m_mp, &m_mp));
m_hash = {};
}
void SignedBigInteger::set_to_0()
{
mp_zero(&m_mp);
m_hash = {};
}
void SignedBigInteger::set_to(i64 other)
{
mp_set_i64(&m_mp, other);
m_hash = {};
}
void SignedBigInteger::set_to(SignedBigInteger const& other)
{
MP_MUST(mp_copy(&other.m_mp, &m_mp));
m_hash = {};
}
size_t SignedBigInteger::byte_length() const
{
return mp_sbin_size(&m_mp);
}
FLATTEN SignedBigInteger SignedBigInteger::plus(SignedBigInteger const& other) const
{
// If both are of the same sign, just add the unsigned data and return.
if (m_sign == other.m_sign)
return { other.m_unsigned_data.plus(m_unsigned_data), m_sign };
// One value is signed while the other is not.
return m_sign ? other.minus(this->m_unsigned_data) : minus(other.m_unsigned_data);
SignedBigInteger result;
MP_MUST(mp_add(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::minus(SignedBigInteger const& other) const
{
// If the signs are different, convert the op to an addition.
if (m_sign != other.m_sign) {
// -x - y = - (x + y)
// x - -y = (x + y)
SignedBigInteger result { other.m_unsigned_data.plus(this->m_unsigned_data) };
if (m_sign)
result.negate();
SignedBigInteger result;
MP_MUST(mp_sub(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
if (!m_sign) {
// Both operands are positive.
// x - y = - (y - x)
if (m_unsigned_data < other.m_unsigned_data) {
// The result will be negative.
return { MUST(other.m_unsigned_data.minus(m_unsigned_data)), true };
}
// The result will be either zero, or positive.
return SignedBigInteger { MUST(m_unsigned_data.minus(other.m_unsigned_data)) };
}
// Both operands are negative.
// -x - -y = y - x
if (m_unsigned_data < other.m_unsigned_data) {
// The result will be positive.
return SignedBigInteger { MUST(other.m_unsigned_data.minus(m_unsigned_data)) };
}
// y - x = - (x - y)
if (m_unsigned_data > other.m_unsigned_data) {
// The result will be negative.
return SignedBigInteger { MUST(m_unsigned_data.minus(other.m_unsigned_data)), true };
}
// Both operands have the same magnitude, the result is positive zero.
return SignedBigInteger { 0 };
}
FLATTEN SignedBigInteger SignedBigInteger::plus(UnsignedBigInteger const& other) const
{
if (m_sign) {
if (other < m_unsigned_data)
return { MUST(m_unsigned_data.minus(other)), true };
return { MUST(other.minus(m_unsigned_data)), false };
}
return { m_unsigned_data.plus(other), false };
SignedBigInteger result;
MP_MUST(mp_add(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::minus(UnsignedBigInteger const& other) const
{
if (m_sign)
return { m_unsigned_data.plus(m_unsigned_data), true };
if (other < m_unsigned_data)
return { MUST(m_unsigned_data.minus(other)), false };
return { MUST(other.minus(m_unsigned_data)), true };
SignedBigInteger result;
MP_MUST(mp_sub(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::bitwise_not() const
{
// Bitwise operators assume two's complement, while SignedBigInteger uses sign-magnitude.
// In two's complement, -x := ~x + 1.
// Hence, ~x == -x -1 == -(x + 1).
SignedBigInteger result = plus(SignedBigInteger { 1 });
result.negate();
SignedBigInteger result;
MP_MUST(mp_complement(&m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const
{
return { unsigned_value().multiplied_by(other), m_sign };
SignedBigInteger result;
MP_MUST(mp_mul(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedDivisionResult SignedBigInteger::divided_by(UnsignedBigInteger const& divisor) const
{
auto division_result = unsigned_value().divided_by(divisor);
return {
{ move(division_result.quotient), m_sign },
{ move(division_result.remainder), m_sign },
};
SignedBigInteger quotient;
SignedBigInteger remainder;
MP_MUST(mp_div(&m_mp, &divisor.m_mp, &quotient.m_mp, &remainder.m_mp));
return SignedDivisionResult { quotient, remainder };
}
FLATTEN SignedBigInteger SignedBigInteger::bitwise_or(SignedBigInteger const& other) const
{
// See bitwise_and() for derivations.
if (!is_negative() && !other.is_negative())
return { unsigned_value().bitwise_or(other.unsigned_value()), false };
// -A | B == (~A + 1) | B == ~(A - 1) | B. The result is negative, so need to two's complement at the end to move the sign into the m_sign field.
// That can be simplified to:
// -(-A | B) == ~(~(A - 1) | B) + 1 = (A - 1) & ~B + 1
// This saves one ~.
if (is_negative() && !other.is_negative()) {
size_t index = unsigned_value().one_based_index_of_highest_set_bit();
return { MUST(unsigned_value().minus(1)).bitwise_and(other.unsigned_value().bitwise_not_fill_to_one_based_index(index)).plus(1), true };
}
// -(A | -B) == ~A & (B - 1) + 1
if (!is_negative() && other.is_negative()) {
size_t index = other.unsigned_value().one_based_index_of_highest_set_bit();
return { unsigned_value().bitwise_not_fill_to_one_based_index(index).bitwise_and(MUST(other.unsigned_value().minus(1))).plus(1), true };
}
return { MUST(unsigned_value().minus(1)).bitwise_and(MUST(other.unsigned_value().minus(1))).plus(1), true };
SignedBigInteger result;
MP_MUST(mp_or(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::bitwise_and(SignedBigInteger const& other) const
{
if (!is_negative() && !other.is_negative())
return { unsigned_value().bitwise_and(other.unsigned_value()), false };
// These two just use that -x == ~x + 1 (see below).
// -A & B == (~A + 1) & B.
if (is_negative() && !other.is_negative()) {
size_t index = other.unsigned_value().one_based_index_of_highest_set_bit();
return { unsigned_value().bitwise_not_fill_to_one_based_index(index).plus(1).bitwise_and(other.unsigned_value()), false };
}
// A & -B == A & (~B + 1).
if (!is_negative() && other.is_negative()) {
size_t index = unsigned_value().one_based_index_of_highest_set_bit();
return { unsigned_value().bitwise_and(other.unsigned_value().bitwise_not_fill_to_one_based_index(index).plus(1)), false };
}
// Both numbers are negative.
// x + ~x == 0xff...ff, up to however many bits x is wide.
// In two's complement, x + ~x + 1 == 0 since the 1 in the overflowing bit position is masked out.
// Rearranging terms, ~x = -x - 1 (eq1).
// Substituting x = y - 1, ~(y - 1) == -(y - 1) - 1 == -y +1 -1 == -y, or ~(y - 1) == -y (eq2).
// Since both numbers are negative, we want to compute -A & -B.
// Per (eq2):
// -A & -B == ~(A - 1) & ~(B - 1)
// Inverting both sides:
// ~(-A & -B) == ~(~(A - 1) & ~(B - 1)) == ~~(A - 1) | ~~(B - 1) == (A - 1) | (B - 1).
// Applying (q1) on the LHS:
// -(-A & -B) - 1 == (A - 1) | (B - 1)
// Adding 1 on both sides and then multiplying both sides by -1:
// -A & -B == -( (A - 1) | (B - 1) + 1)
// So we can compute the bitwise and by returning a negative number with magnitude (A - 1) | (B - 1) + 1.
// This is better than the naive (~A + 1) & (~B + 1) because it needs just one O(n) scan for the or instead of 2 for the ~s.
return { MUST(unsigned_value().minus(1)).bitwise_or(MUST(other.unsigned_value().minus(1))).plus(1), true };
SignedBigInteger result;
MP_MUST(mp_and(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::bitwise_xor(SignedBigInteger const& other) const
@ -247,23 +245,17 @@ FLATTEN SignedBigInteger SignedBigInteger::bitwise_xor(SignedBigInteger const& o
bool SignedBigInteger::operator==(UnsignedBigInteger const& other) const
{
if (m_sign && m_unsigned_data != 0)
return false;
return m_unsigned_data == other;
return mp_cmp(&m_mp, &other.m_mp) == MP_EQ;
}
bool SignedBigInteger::operator!=(UnsignedBigInteger const& other) const
{
if (m_sign)
return true;
return m_unsigned_data != other;
return !(*this == other);
}
bool SignedBigInteger::operator<(UnsignedBigInteger const& other) const
{
if (m_sign)
return true;
return m_unsigned_data < other;
return mp_cmp(&m_mp, &other.m_mp) == MP_LT;
}
bool SignedBigInteger::operator>(UnsignedBigInteger const& other) const
@ -271,67 +263,65 @@ bool SignedBigInteger::operator>(UnsignedBigInteger const& other) const
return *this != other && !(*this < other);
}
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::try_shift_left(size_t num_bits) const
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::shift_left(size_t num_bits) const
{
return SignedBigInteger { TRY(m_unsigned_data.try_shift_left(num_bits)), m_sign };
}
FLATTEN SignedBigInteger SignedBigInteger::shift_left(size_t num_bits) const
{
return MUST(try_shift_left(num_bits));
SignedBigInteger result;
MP_TRY(mp_mul_2d(&m_mp, num_bits, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::shift_right(size_t num_bits) const
{
return SignedBigInteger { m_unsigned_data.shift_right(num_bits), m_sign };
SignedBigInteger result;
MP_MUST(mp_div_2d(&m_mp, num_bits, &result.m_mp, nullptr));
return result;
}
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::mod_power_of_two(size_t power_of_two) const
{
auto const lower_bits = m_unsigned_data.as_n_bits(power_of_two);
if (power_of_two == 0)
return SignedBigInteger(0);
if (is_positive())
return SignedBigInteger(lower_bits);
// If the number is positive and smaller than the modulus, we can just return it.
if (!is_negative() && static_cast<size_t>(m_mp.used * MP_DIGIT_BIT) <= power_of_two)
return *this;
// twos encode lower bits
return SignedBigInteger(TRY(lower_bits.try_bitwise_not_fill_to_one_based_index(power_of_two)).plus(1).as_n_bits(power_of_two));
// If the power of two overflows the int type, we don't have enough memory to compute it.
if (power_of_two > NumericLimits<int>::max())
return Error::from_errno(ENOMEM);
SignedBigInteger result;
MP_MUST(mp_mod_2d(&m_mp, power_of_two, &result.m_mp));
if (!result.is_negative())
return result;
// If the result is negative, we need to add the modulus to it.
UnsignedBigInteger modulus;
MP_TRY(mp_2expt(&modulus.m_mp, power_of_two));
MP_MUST(mp_add(&result.m_mp, &modulus.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(SignedBigInteger const& other) const
{
bool result_sign = m_sign ^ other.m_sign;
return { m_unsigned_data.multiplied_by(other.m_unsigned_data), result_sign };
SignedBigInteger result;
MP_MUST(mp_mul(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN SignedDivisionResult SignedBigInteger::divided_by(SignedBigInteger const& divisor) const
{
// Aa / Bb -> (A^B)q, Ar
bool result_sign = m_sign ^ divisor.m_sign;
auto unsigned_division_result = m_unsigned_data.divided_by(divisor.m_unsigned_data);
return {
{ move(unsigned_division_result.quotient), result_sign },
{ move(unsigned_division_result.remainder), m_sign }
};
SignedBigInteger quotient;
SignedBigInteger remainder;
MP_MUST(mp_div(&m_mp, &divisor.m_mp, &quotient.m_mp, &remainder.m_mp));
return SignedDivisionResult { quotient, remainder };
}
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;
SignedBigInteger result;
MP_MUST(mp_expt_n(&m_mp, exponent, &result.m_mp));
return result;
}
FLATTEN SignedBigInteger SignedBigInteger::negated_value() const
@ -343,20 +333,18 @@ FLATTEN SignedBigInteger SignedBigInteger::negated_value() const
u32 SignedBigInteger::hash() const
{
return m_unsigned_data.hash() * (1 - (2 * m_sign));
}
if (m_hash.has_value())
return *m_hash;
void SignedBigInteger::set_bit_inplace(size_t bit_index)
{
m_unsigned_data.set_bit_inplace(bit_index);
auto buffer = MUST(ByteBuffer::create_zeroed(byte_length()));
auto length = export_data(buffer);
m_hash = string_hash(reinterpret_cast<char const*>(buffer.data()), length);
return *m_hash;
}
bool SignedBigInteger::operator==(SignedBigInteger const& other) const
{
if (m_unsigned_data == 0 && other.m_unsigned_data == 0)
return true;
return m_sign == other.m_sign && m_unsigned_data == other.m_unsigned_data;
return mp_cmp(&m_mp, &other.m_mp) == MP_EQ;
}
bool SignedBigInteger::operator!=(SignedBigInteger const& other) const
@ -366,13 +354,7 @@ bool SignedBigInteger::operator!=(SignedBigInteger const& other) const
bool SignedBigInteger::operator<(SignedBigInteger const& other) const
{
if (m_sign ^ other.m_sign)
return m_sign;
if (m_sign)
return other.m_unsigned_data < m_unsigned_data;
return m_unsigned_data < other.m_unsigned_data;
return mp_cmp(&m_mp, &other.m_mp) == MP_LT;
}
bool SignedBigInteger::operator<=(SignedBigInteger const& other) const
@ -392,7 +374,7 @@ bool SignedBigInteger::operator>=(SignedBigInteger const& other) const
UnsignedBigInteger::CompareResult SignedBigInteger::compare_to_double(double value) const
{
bool bigint_is_negative = m_sign;
bool bigint_is_negative = is_negative();
bool value_is_negative = value < 0;
@ -400,9 +382,9 @@ UnsignedBigInteger::CompareResult SignedBigInteger::compare_to_double(double val
return bigint_is_negative ? UnsignedBigInteger::CompareResult::DoubleGreaterThanBigInt : UnsignedBigInteger::CompareResult::DoubleLessThanBigInt;
// Now both bigint and value have the same sign, so let's compare our magnitudes.
auto magnitudes_compare_result = m_unsigned_data.compare_to_double(fabs(value));
auto magnitudes_compare_result = unsigned_value().compare_to_double(fabs(value));
// If our mangnitudes are euqal, then we're equal.
// If our magnitudes are equal, then we're equal.
if (magnitudes_compare_result == UnsignedBigInteger::CompareResult::DoubleEqualsBigInt)
return UnsignedBigInteger::CompareResult::DoubleEqualsBigInt;

View file

@ -1,14 +1,13 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/Span.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
namespace Crypto {
@ -18,44 +17,27 @@ struct SignedDivisionResult;
class SignedBigInteger {
public:
template<Signed T>
requires(sizeof(T) <= sizeof(i32))
SignedBigInteger(T value)
: m_sign(value < 0)
, m_unsigned_data(static_cast<u32>(abs(static_cast<i64>(value))))
{
}
SignedBigInteger(UnsignedBigInteger&& unsigned_data, bool sign)
: m_sign(sign)
, m_unsigned_data(move(unsigned_data))
{
ensure_sign_is_valid();
}
explicit SignedBigInteger(UnsignedBigInteger unsigned_data)
: m_sign(false)
, m_unsigned_data(move(unsigned_data))
{
}
SignedBigInteger()
: m_sign(false)
, m_unsigned_data()
: SignedBigInteger(static_cast<i64>(value))
{
}
SignedBigInteger(UnsignedBigInteger&& unsigned_data, bool sign);
SignedBigInteger(u8 const* ptr, size_t length);
explicit SignedBigInteger(UnsignedBigInteger const& unsigned_data);
explicit SignedBigInteger(double value);
explicit SignedBigInteger(i64 value);
explicit SignedBigInteger(i64 value)
: m_sign(value < 0)
, m_unsigned_data(value < 0 ? static_cast<u64>(-(value + 1)) + 1 : static_cast<u64>(value))
{
}
SignedBigInteger(SignedBigInteger const&);
SignedBigInteger& operator=(SignedBigInteger const&);
[[nodiscard]] static SignedBigInteger import_data(StringView data) { return import_data((u8 const*)data.characters_without_null_termination(), data.length()); }
[[nodiscard]] static SignedBigInteger import_data(u8 const* ptr, size_t length);
SignedBigInteger();
~SignedBigInteger();
size_t export_data(Bytes, bool remove_leading_zeros = false) const;
[[nodiscard]] static SignedBigInteger import_data(StringView data) { return import_data(reinterpret_cast<u8 const*>(data.characters_without_null_termination()), data.length()); }
[[nodiscard]] static SignedBigInteger import_data(u8 const* ptr, size_t length) { return SignedBigInteger(ptr, length); }
size_t export_data(Bytes) const;
[[nodiscard]] static ErrorOr<SignedBigInteger> from_base(u16 N, StringView str);
[[nodiscard]] ErrorOr<String> to_base(u16 N) const;
@ -63,38 +45,17 @@ public:
[[nodiscard]] u64 to_u64() const;
[[nodiscard]] double to_double(UnsignedBigInteger::RoundingMode rounding_mode = UnsignedBigInteger::RoundingMode::IEEERoundAndTiesToEvenMantissa) const;
[[nodiscard]] UnsignedBigInteger const& unsigned_value() const { return m_unsigned_data; }
[[nodiscard]] Vector<u32, STARTING_WORD_SIZE> const words() const { return m_unsigned_data.words(); }
[[nodiscard]] UnsignedBigInteger unsigned_value() const;
[[nodiscard]] bool is_positive() const { return !is_negative() && !is_zero(); }
[[nodiscard]] bool is_negative() const { return m_sign; }
[[nodiscard]] bool is_zero() const { return m_unsigned_data.is_zero(); }
[[nodiscard]] bool is_negative() const;
[[nodiscard]] bool is_zero() const;
void negate()
{
if (!m_unsigned_data.is_zero())
m_sign = !m_sign;
}
void negate();
void set_to_0();
void set_to(i64 other);
void set_to(SignedBigInteger const& other);
void set_to_0()
{
m_unsigned_data.set_to_0();
m_sign = false;
}
void set_to(i32 other)
{
m_unsigned_data.set_to((u32)other);
m_sign = other < 0;
}
void set_to(SignedBigInteger const& other)
{
m_unsigned_data.set_to(other.m_unsigned_data);
m_sign = other.m_sign;
}
// These get + 1 byte for the sign.
[[nodiscard]] size_t length() const { return m_unsigned_data.length() + 1; }
[[nodiscard]] size_t trimmed_length() const { return m_unsigned_data.trimmed_length() + 1; }
[[nodiscard]] size_t byte_length() const;
[[nodiscard]] SignedBigInteger plus(SignedBigInteger const& other) const;
[[nodiscard]] SignedBigInteger minus(SignedBigInteger const& other) const;
@ -102,14 +63,12 @@ public:
[[nodiscard]] SignedBigInteger bitwise_and(SignedBigInteger const& other) const;
[[nodiscard]] SignedBigInteger bitwise_xor(SignedBigInteger const& other) const;
[[nodiscard]] SignedBigInteger bitwise_not() const;
[[nodiscard]] SignedBigInteger shift_left(size_t num_bits) const;
[[nodiscard]] ErrorOr<SignedBigInteger> shift_left(size_t num_bits) const;
[[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;
[[nodiscard]] SignedBigInteger plus(UnsignedBigInteger const& other) const;
[[nodiscard]] SignedBigInteger minus(UnsignedBigInteger const& other) const;
@ -120,8 +79,6 @@ public:
[[nodiscard]] u32 hash() const;
void set_bit_inplace(size_t bit_index);
[[nodiscard]] bool operator==(SignedBigInteger const& other) const;
[[nodiscard]] bool operator!=(SignedBigInteger const& other) const;
[[nodiscard]] bool operator<(SignedBigInteger const& other) const;
@ -137,14 +94,8 @@ public:
[[nodiscard]] UnsignedBigInteger::CompareResult compare_to_double(double) const;
private:
void ensure_sign_is_valid()
{
if (m_sign && m_unsigned_data.is_zero())
m_sign = false;
}
bool m_sign { false };
UnsignedBigInteger m_unsigned_data;
mp_int m_mp {};
mutable Optional<u32> m_hash {};
};
struct SignedDivisionResult {

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Error.h>
#include <tommath.h>
inline ErrorOr<void> mp_error(mp_err error)
{
switch (error) {
case MP_OKAY:
return {};
case MP_MEM:
return Error::from_errno(ENOMEM);
case MP_VAL:
return Error::from_errno(EINVAL);
case MP_ITER:
return Error::from_string_literal("Maximum iterations reached");
case MP_BUF:
return Error::from_string_literal("Buffer overflow");
default:
return Error::from_string_literal("Unknown error");
}
}
#define MP_TRY(...) TRY(mp_error((__VA_ARGS__)))
#define MP_MUST(...) MUST(mp_error((__VA_ARGS__)))

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
extern "C" {
typedef uint64_t mp_digit;
typedef int mp_sign;
// This is a workaround for the fact that Tommath doesn't have a proper
// header file. It uses an anonymous struct to define the mp_int struct
// which makes it impossible to forward declare.
// Declare the mp_int struct with the same layout as the one in tommath.h
// and check that the layout is the same while avoiding a conflicting
// definition error (BN_H_ is defined in tommath.h).
// When importing this file it is important to always do it AFTER tommath.h.
typedef struct {
int used, alloc;
mp_sign sign;
mp_digit* dp;
} mp_int_;
#ifndef BN_H_
typedef mp_int_ mp_int;
#else
static_assert(sizeof(mp_int_) == sizeof(mp_int));
static_assert(offsetof(mp_int_, used) == offsetof(mp_int, used));
static_assert(offsetof(mp_int_, alloc) == offsetof(mp_int, alloc));
static_assert(offsetof(mp_int_, sign) == offsetof(mp_int, sign));
static_assert(offsetof(mp_int_, dp) == offsetof(mp_int, dp));
#endif
}

View file

@ -1,38 +1,33 @@
/*
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "UnsignedBigInteger.h"
#include <AK/BuiltinWrappers.h>
#include <AK/CharacterTypes.h>
#include <AK/FloatingPoint.h>
#include <AK/StringBuilder.h>
#include <AK/StringHash.h>
#include <LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h>
#include <math.h>
#include <tommath.h>
#include <AK/BuiltinWrappers.h>
#include <AK/FloatingPoint.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/StringBuilder.h>
#include <LibCrypto/BigInt/Tommath.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
namespace Crypto {
UnsignedBigInteger::UnsignedBigInteger(u8 const* ptr, size_t length)
{
m_words.resize_and_keep_capacity((length + sizeof(u32) - 1) / sizeof(u32));
size_t in = length, out = 0;
while (in >= sizeof(u32)) {
in -= sizeof(u32);
u32 word = ((u32)ptr[in] << 24) | ((u32)ptr[in + 1] << 16) | ((u32)ptr[in + 2] << 8) | (u32)ptr[in + 3];
m_words[out++] = word;
}
if (in > 0) {
u32 word = 0;
for (size_t i = 0; i < in; i++) {
word <<= 8;
word |= (u32)ptr[i];
}
m_words[out++] = word;
MP_MUST(mp_init(&m_mp));
MP_MUST(mp_from_ubin(&m_mp, ptr, length));
}
UnsignedBigInteger::UnsignedBigInteger(Vector<u32> const& words)
{
MP_MUST(mp_init(&m_mp));
MP_MUST(mp_unpack(&m_mp, words.size(), MP_LSB_FIRST, sizeof(u32), MP_NATIVE_ENDIAN, 0, words.data()));
}
UnsignedBigInteger::UnsignedBigInteger(double value)
@ -44,489 +39,265 @@ UnsignedBigInteger::UnsignedBigInteger(double value)
VERIFY(trunc(value) == value);
VERIFY(value >= 0.0);
if (value <= NumericLimits<u32>::max()) {
m_words.append(static_cast<u32>(value));
return;
MP_MUST(mp_init(&m_mp));
MP_MUST(mp_set_double(&m_mp, value));
}
FloatExtractor<double> extractor;
extractor.d = value;
VERIFY(!extractor.sign);
i32 real_exponent = extractor.exponent - extractor.exponent_bias;
VERIFY(real_exponent > 0);
// Ensure we have enough space, we will need 2^exponent bits, so round up in words
auto word_index = (real_exponent + BITS_IN_WORD) / BITS_IN_WORD;
m_words.resize_and_keep_capacity(word_index);
// Now we just need to put the mantissa with explicit 1 bit at the top at the proper location
u64 raw_mantissa = extractor.mantissa | (1ull << extractor.mantissa_bits);
VERIFY((raw_mantissa & 0xfff0000000000000) == 0x0010000000000000);
// Shift it so the bits we need are at the top
raw_mantissa <<= 64 - extractor.mantissa_bits - 1;
// The initial bit needs to be exactly aligned with exponent, this is 1-indexed
auto top_word_bit_offset = real_exponent % BITS_IN_WORD + 1;
auto top_word_bits_from_mantissa = raw_mantissa >> (64 - top_word_bit_offset);
VERIFY(top_word_bits_from_mantissa <= NumericLimits<Word>::max());
m_words[word_index - 1] = top_word_bits_from_mantissa;
--word_index;
// Shift used bits away
raw_mantissa <<= top_word_bit_offset;
i32 bits_in_mantissa = extractor.mantissa_bits + 1 - top_word_bit_offset;
// Now just put everything at the top of the next words
constexpr auto to_word_shift = 64 - BITS_IN_WORD;
while (word_index > 0 && bits_in_mantissa > 0) {
VERIFY((raw_mantissa >> to_word_shift) <= NumericLimits<Word>::max());
m_words[word_index - 1] = raw_mantissa >> to_word_shift;
raw_mantissa <<= to_word_shift;
bits_in_mantissa -= BITS_IN_WORD;
--word_index;
}
VERIFY(m_words.size() > word_index);
VERIFY((m_words.size() - word_index) <= 3);
// No bits left, otherwise we would have to round
VERIFY(raw_mantissa == 0);
}
size_t UnsignedBigInteger::export_data(Bytes data, bool remove_leading_zeros) const
UnsignedBigInteger::UnsignedBigInteger(u64 value)
{
size_t word_count = trimmed_length();
size_t out = 0;
if (word_count > 0) {
ssize_t leading_zeros = -1;
if (remove_leading_zeros) {
UnsignedBigInteger::Word word = m_words[word_count - 1];
for (size_t i = 0; i < sizeof(u32); i++) {
u8 byte = (u8)(word >> ((sizeof(u32) - i - 1) * 8));
data[out++] = byte;
if (leading_zeros < 0 && byte != 0)
leading_zeros = (int)i;
MP_MUST(mp_init(&m_mp));
mp_set_u64(&m_mp, value);
}
UnsignedBigInteger::UnsignedBigInteger(UnsignedBigInteger const& other)
{
MP_MUST(mp_init_copy(&m_mp, &other.m_mp));
}
for (size_t i = word_count - (remove_leading_zeros ? 1 : 0); i > 0; i--) {
auto word = m_words[i - 1];
data[out++] = (u8)(word >> 24);
data[out++] = (u8)(word >> 16);
data[out++] = (u8)(word >> 8);
data[out++] = (u8)word;
UnsignedBigInteger& UnsignedBigInteger::operator=(UnsignedBigInteger const& other)
{
if (this == &other)
return *this;
mp_clear(&m_mp);
MP_MUST(mp_init_copy(&m_mp, &other.m_mp));
return *this;
}
if (leading_zeros > 0)
out -= leading_zeros;
UnsignedBigInteger::UnsignedBigInteger()
{
MP_MUST(mp_init(&m_mp));
}
return out;
UnsignedBigInteger::~UnsignedBigInteger()
{
mp_clear(&m_mp);
}
size_t UnsignedBigInteger::export_data(Bytes data) const
{
size_t written = 0;
MP_MUST(mp_to_ubin(&m_mp, data.data(), data.size(), &written));
return written;
}
ErrorOr<UnsignedBigInteger> UnsignedBigInteger::from_base(u16 N, StringView str)
{
VERIFY(N <= 36);
UnsignedBigInteger result;
UnsignedBigInteger base { N };
if (str.is_empty())
return UnsignedBigInteger(0);
for (auto const& c : str) {
if (c == '_')
auto buffer = TRY(ByteBuffer::create_zeroed(str.length() + 1));
size_t idx = 0;
for (auto& c : str) {
if (c == '_') {
// Skip underscores
continue;
if (!is_ascii_base36_digit(c))
return Error::from_string_literal("Invalid Base36 digit");
auto digit = parse_ascii_base36_digit(c);
if (digit >= N)
return Error::from_string_literal("Base36 digit out of range");
result = result.multiplied_by(base).plus(digit);
}
buffer[idx++] = c;
}
UnsignedBigInteger result;
if (mp_read_radix(&result.m_mp, reinterpret_cast<char const*>(buffer.data()), N) != MP_OKAY)
return Error::from_string_literal("Invalid number");
return result;
}
ErrorOr<String> UnsignedBigInteger::to_base(u16 N) const
{
VERIFY(N <= 36);
if (*this == UnsignedBigInteger { 0 })
if (is_zero())
return "0"_string;
StringBuilder builder;
UnsignedBigInteger temp(*this);
UnsignedBigInteger quotient;
UnsignedBigInteger remainder;
int size = 0;
MP_MUST(mp_radix_size(&m_mp, N, &size));
auto buffer = TRY(ByteBuffer::create_zeroed(size));
while (temp != UnsignedBigInteger { 0 }) {
UnsignedBigIntegerAlgorithms::divide_u16_without_allocation(temp, N, quotient, remainder);
VERIFY(remainder.words()[0] < N);
TRY(builder.try_append(to_ascii_base36_digit(remainder.words()[0])));
temp.set_to(quotient);
}
size_t written = 0;
MP_MUST(mp_to_radix(&m_mp, reinterpret_cast<char*>(buffer.data()), size, &written, N));
return TRY(builder.to_string()).reverse();
return StringView(buffer.bytes().slice(0, written - 1)).to_ascii_lowercase_string();
}
u64 UnsignedBigInteger::to_u64() const
{
static_assert(sizeof(Word) == 4);
if (!length())
return 0;
u64 value = m_words[0];
if (length() > 1)
value |= static_cast<u64>(m_words[1]) << 32;
return value;
return mp_get_u64(&m_mp);
}
double UnsignedBigInteger::to_double(UnsignedBigInteger::RoundingMode rounding_mode) const
double UnsignedBigInteger::to_double(RoundingMode rounding_mode) const
{
auto highest_bit = one_based_index_of_highest_set_bit();
if (highest_bit == 0)
return 0;
--highest_bit;
// Check if we need to truncate
auto bitlen = mp_count_bits(&m_mp);
if (bitlen <= 53)
return mp_get_double(&m_mp);
using Extractor = FloatExtractor<double>;
if (rounding_mode == RoundingMode::RoundTowardZero) {
UnsignedBigInteger shifted;
// Simple case if less than 2^53 since those number are all exactly representable in doubles
if (highest_bit < Extractor::mantissa_bits + 1)
return static_cast<double>(to_u64());
// Truncate the lower bits
auto shift = bitlen - 53;
MP_MUST(mp_div_2d(&m_mp, shift, &shifted.m_mp, nullptr));
// If it uses too many bit to represent in a double return infinity
if (highest_bit > Extractor::exponent_bias)
return __builtin_huge_val();
// Otherwise we have to take the top 53 bits, use those as the mantissa,
// and the amount of bits as the exponent. Note that the mantissa has an implicit top bit of 1
// so we have to ignore the very top bit.
// Since we extract at most 53 bits it will take at most 3 words
static_assert(BITS_IN_WORD * 3 >= (Extractor::mantissa_bits + 1));
constexpr auto bits_in_u64 = 64;
static_assert(bits_in_u64 > Extractor::mantissa_bits + 1);
auto bits_to_read = min(static_cast<size_t>(Extractor::mantissa_bits), highest_bit);
auto last_word_index = trimmed_length();
VERIFY(last_word_index > 0);
// Note that highest bit is 0-indexed at this point.
auto highest_bit_index_in_top_word = highest_bit % BITS_IN_WORD;
// Shift initial word until highest bit is just beyond top of u64.
u64 mantissa = m_words[last_word_index - 1];
if (highest_bit_index_in_top_word != 0)
mantissa <<= (bits_in_u64 - highest_bit_index_in_top_word);
else
mantissa = 0;
auto bits_written = highest_bit_index_in_top_word;
--last_word_index;
Optional<Word> dropped_bits_for_rounding;
u8 bits_dropped_from_final_word = 0;
if (bits_written < bits_to_read && last_word_index > 0) {
// Second word can always just cleanly be shifted up to the final bit of the first word
// since the first has at most BIT_IN_WORD - 1, 31
u64 next_word = m_words[last_word_index - 1];
VERIFY((mantissa & (next_word << (bits_in_u64 - bits_written - BITS_IN_WORD))) == 0);
mantissa |= next_word << (bits_in_u64 - bits_written - BITS_IN_WORD);
bits_written += BITS_IN_WORD;
--last_word_index;
if (bits_written > bits_to_read) {
bits_dropped_from_final_word = bits_written - bits_to_read;
dropped_bits_for_rounding = m_words[last_word_index] & ((1 << bits_dropped_from_final_word) - 1);
} else if (bits_written < bits_to_read && last_word_index > 0) {
// The final word has to be shifted down first to discard any excess bits.
u64 final_word = m_words[last_word_index - 1];
--last_word_index;
auto bits_to_write = bits_to_read - bits_written;
bits_dropped_from_final_word = BITS_IN_WORD - bits_to_write;
dropped_bits_for_rounding = final_word & ((1 << bits_dropped_from_final_word) - 1u);
final_word >>= bits_dropped_from_final_word;
// Then move the bits right up to the lowest bits of the second word
VERIFY((mantissa & (final_word << (bits_in_u64 - bits_written - bits_to_write))) == 0);
mantissa |= final_word << (bits_in_u64 - bits_written - bits_to_write);
// Convert to double
return ldexp(mp_get_double(&shifted.m_mp), shift);
}
}
// Now the mantissa should be complete so shift it down
mantissa >>= bits_in_u64 - Extractor::mantissa_bits;
if (rounding_mode == RoundingMode::IEEERoundAndTiesToEvenMantissa) {
bool round_up = false;
UnsignedBigInteger shifted, remainder, half, lsb;
if (bits_dropped_from_final_word == 0) {
if (last_word_index > 0) {
Word next_word = m_words[last_word_index - 1];
last_word_index--;
if ((next_word & 0x80000000) != 0) {
// next top bit set check for any other bits
if ((next_word ^ 0x80000000) != 0) {
round_up = true;
// Get top 53 bits (truncated)
auto shift = bitlen - 53;
MP_MUST(mp_div_2d(&m_mp, shift, &shifted.m_mp, &remainder.m_mp));
// Check if remainder == 2^(shift - 1)
MP_MUST(mp_2expt(&half.m_mp, shift - 1));
int cmp = mp_cmp(&remainder.m_mp, &half.m_mp);
if (cmp < 0) {
// Round down (truncate)
} else if (cmp > 0) {
// Round up
MP_MUST(mp_add_d(&shifted.m_mp, 1, &shifted.m_mp));
} else {
while (last_word_index > 0) {
if (m_words[last_word_index - 1] != 0) {
round_up = true;
break;
// Exactly halfway, check even
MP_MUST(mp_mod_2d(&shifted.m_mp, 1, &lsb.m_mp));
if (!mp_iszero(&lsb.m_mp)) {
// It's odd, round to even
MP_MUST(mp_add_d(&shifted.m_mp, 1, &shifted.m_mp));
}
}
// All other bits are 0 which is a tie thus round to even exponent
// Since we are halfway, if exponent ends with 1 we round up, if 0 we round down
round_up = (mantissa & 1) != 0;
}
} else {
round_up = false;
}
} else {
// If there are no words left the rest is implicitly 0 so just round down
round_up = false;
// Convert to double
return ldexp(mp_get_double(&shifted.m_mp), shift);
}
} else {
VERIFY(dropped_bits_for_rounding.has_value());
VERIFY(bits_dropped_from_final_word >= 1);
// In this case the top bit comes form the dropped bits
auto top_bit_extractor = 1u << (bits_dropped_from_final_word - 1u);
if ((*dropped_bits_for_rounding & top_bit_extractor) != 0) {
// Possible tie again, if any other bit is set we round up
if ((*dropped_bits_for_rounding ^ top_bit_extractor) != 0) {
round_up = true;
} else {
while (last_word_index > 0) {
if (m_words[last_word_index - 1] != 0) {
round_up = true;
break;
}
VERIFY_NOT_REACHED();
}
round_up = (mantissa & 1) != 0;
}
} else {
round_up = false;
}
}
Vector<u32> UnsignedBigInteger::words() const
{
auto count = mp_pack_count(&m_mp, 0, sizeof(u32));
Vector<u32> result;
result.resize(count);
if (round_up) {
++mantissa;
if ((mantissa & (1ull << Extractor::mantissa_bits)) != 0) {
// we overflowed the mantissa
mantissa = 0;
highest_bit++;
size_t written = 0;
MP_MUST(mp_pack(result.data(), count, &written, MP_LSB_FIRST, sizeof(u32), MP_NATIVE_ENDIAN, 0, &m_mp));
// In which case it is possible we have to round to infinity
if (highest_bit > Extractor::exponent_bias)
return __builtin_huge_val();
}
}
} else {
VERIFY(rounding_mode == RoundingMode::RoundTowardZero);
}
Extractor extractor;
extractor.exponent = highest_bit + extractor.exponent_bias;
VERIFY((mantissa & 0xfff0000000000000) == 0);
extractor.mantissa = mantissa;
return extractor.d;
result.resize(written);
return result;
}
void UnsignedBigInteger::set_to_0()
{
m_words.clear_with_capacity();
m_cached_trimmed_length = {};
m_cached_hash = 0;
mp_zero(&m_mp);
m_hash = {};
}
void UnsignedBigInteger::set_to(UnsignedBigInteger::Word other)
void UnsignedBigInteger::set_to(u64 other)
{
m_words.resize_and_keep_capacity(1);
m_words[0] = other;
m_cached_trimmed_length = {};
m_cached_hash = 0;
mp_set_u64(&m_mp, other);
m_hash = {};
}
void UnsignedBigInteger::set_to(UnsignedBigInteger const& other)
{
m_words.resize_and_keep_capacity(other.m_words.size());
__builtin_memcpy(m_words.data(), other.m_words.data(), other.m_words.size() * sizeof(u32));
m_cached_trimmed_length = {};
m_cached_hash = 0;
MP_MUST(mp_copy(&other.m_mp, &m_mp));
m_hash = {};
}
bool UnsignedBigInteger::is_zero() const
{
for (size_t i = 0; i < length(); ++i) {
if (m_words[i] != 0)
return false;
return mp_iszero(&m_mp);
}
return true;
}
size_t UnsignedBigInteger::trimmed_length() const
bool UnsignedBigInteger::is_odd() const
{
if (!m_cached_trimmed_length.has_value()) {
size_t num_leading_zeroes = 0;
for (int i = length() - 1; i >= 0; --i, ++num_leading_zeroes) {
if (m_words[i] != 0)
break;
}
m_cached_trimmed_length = length() - num_leading_zeroes;
}
return m_cached_trimmed_length.value();
return mp_isodd(&m_mp);
}
void UnsignedBigInteger::clamp_to_trimmed_length()
size_t UnsignedBigInteger::byte_length() const
{
auto length = trimmed_length();
if (m_words.size() > length)
m_words.resize(length);
}
void UnsignedBigInteger::resize_with_leading_zeros(size_t new_length)
{
size_t old_length = length();
if (old_length < new_length) {
m_words.resize_and_keep_capacity(new_length);
__builtin_memset(&m_words.data()[old_length], 0, (new_length - old_length) * sizeof(u32));
}
return mp_ubin_size(&m_mp);
}
size_t UnsignedBigInteger::one_based_index_of_highest_set_bit() const
{
size_t number_of_words = trimmed_length();
size_t index = 0;
if (number_of_words > 0) {
index += (number_of_words - 1) * BITS_IN_WORD;
index += BITS_IN_WORD - count_leading_zeroes(m_words[number_of_words - 1]);
}
return index;
return mp_count_bits(&m_mp);
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::plus(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
UnsignedBigIntegerAlgorithms::add_without_allocation(*this, other, result);
MP_MUST(mp_add(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::minus(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
TRY(UnsignedBigIntegerAlgorithms::subtract_without_allocation(*this, other, result));
MP_MUST(mp_sub(&m_mp, &other.m_mp, &result.m_mp));
if (mp_isneg(&result.m_mp))
return Error::from_string_literal("Substraction produced a negative result");
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_or(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
UnsignedBigIntegerAlgorithms::bitwise_or_without_allocation(*this, other, result);
MP_MUST(mp_or(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_and(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
UnsignedBigIntegerAlgorithms::bitwise_and_without_allocation(*this, other, result);
MP_MUST(mp_and(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
MP_MUST(mp_xor(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation(*this, other, result);
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t index) const
{
if (index == 0)
return UnsignedBigInteger(0);
if (index > NumericLimits<int>::max())
return Error::from_errno(ENOMEM);
UnsignedBigInteger result, mask, temp;
MP_TRY(mp_2expt(&mask.m_mp, index));
MP_TRY(mp_sub_d(&mask.m_mp, 1, &mask.m_mp));
MP_TRY(mp_and(&mask.m_mp, &m_mp, &temp.m_mp));
MP_TRY(mp_xor(&temp.m_mp, &mask.m_mp, &result.m_mp));
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t size) const
{
return MUST(try_bitwise_not_fill_to_one_based_index(size));
}
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::try_bitwise_not_fill_to_one_based_index(size_t size) const
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::shift_left(size_t num_bits) const
{
UnsignedBigInteger result;
TRY(UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result));
MP_TRY(mp_mul_2d(&m_mp, num_bits, &result.m_mp));
return result;
}
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::try_shift_left(size_t num_bits) const
{
UnsignedBigInteger output;
TRY(UnsignedBigIntegerAlgorithms::try_shift_left_without_allocation(*this, num_bits, output));
return output;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_left(size_t num_bits) const
{
return MUST(try_shift_left(num_bits));
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_right(size_t num_bits) const
{
UnsignedBigInteger output;
UnsignedBigIntegerAlgorithms::shift_right_without_allocation(*this, num_bits, output);
return output;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::as_n_bits(size_t n) const
{
if (auto const num_bits = one_based_index_of_highest_set_bit(); n >= num_bits)
return *this;
UnsignedBigInteger output;
output.set_to(*this);
auto const word_index = n / BITS_IN_WORD;
auto const bits_to_keep = n % BITS_IN_WORD;
auto const bits_to_discard = BITS_IN_WORD - bits_to_keep;
output.m_words.resize(word_index + 1);
auto const last_word = output.m_words[word_index];
Word new_last_word = 0;
// avoid UB from a 32 bit shift on a u32
if (bits_to_keep != 0)
new_last_word = last_word << bits_to_discard >> bits_to_discard;
output.m_words[word_index] = new_last_word;
return output;
UnsignedBigInteger result;
MP_MUST(mp_div_2d(&m_mp, num_bits, &result.m_mp, nullptr));
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const
{
UnsignedBigInteger result;
UnsignedBigInteger temp_shift;
UnsignedBigIntegerAlgorithms::multiply_without_allocation(*this, other, temp_shift, result);
MP_MUST(mp_mul(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
@ -534,84 +305,38 @@ FLATTEN UnsignedDivisionResult UnsignedBigInteger::divided_by(UnsignedBigInteger
{
UnsignedBigInteger quotient;
UnsignedBigInteger remainder;
// If we actually have a u16-compatible divisor, short-circuit to the
// less computationally-intensive "divide_u16_without_allocation" method.
if (divisor.trimmed_length() == 1 && divisor.m_words[0] < (1 << 16)) {
UnsignedBigIntegerAlgorithms::divide_u16_without_allocation(*this, divisor.m_words[0], quotient, remainder);
return UnsignedDivisionResult { quotient, remainder };
}
UnsignedBigIntegerAlgorithms::divide_without_allocation(*this, divisor, quotient, remainder);
MP_MUST(mp_div(&m_mp, &divisor.m_mp, &quotient.m_mp, &remainder.m_mp));
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;
UnsignedBigInteger result;
MP_MUST(mp_expt_n(&m_mp, exponent, &result.m_mp));
return result;
}
FLATTEN UnsignedBigInteger UnsignedBigInteger::gcd(UnsignedBigInteger const& other) const
{
UnsignedBigInteger temp_a { *this };
UnsignedBigInteger temp_b { other };
UnsignedBigInteger temp_quotient;
UnsignedBigInteger temp_remainder;
UnsignedBigInteger output;
UnsignedBigIntegerAlgorithms::destructive_GCD_without_allocation(temp_a, temp_b, temp_quotient, temp_remainder, output);
return output;
UnsignedBigInteger result;
MP_MUST(mp_gcd(&m_mp, &other.m_mp, &result.m_mp));
return result;
}
u32 UnsignedBigInteger::hash() const
{
if (m_cached_hash != 0)
return m_cached_hash;
if (m_hash.has_value())
return *m_hash;
return m_cached_hash = string_hash((char const*)m_words.data(), sizeof(Word) * m_words.size());
}
void UnsignedBigInteger::set_bit_inplace(size_t bit_index)
{
size_t const word_index = bit_index / UnsignedBigInteger::BITS_IN_WORD;
size_t const inner_word_index = bit_index % UnsignedBigInteger::BITS_IN_WORD;
m_words.ensure_capacity(word_index + 1);
for (size_t i = length(); i <= word_index; ++i) {
m_words.unchecked_append(0);
}
m_words[word_index] |= (1 << inner_word_index);
m_cached_trimmed_length = {};
m_cached_hash = 0;
auto buffer = MUST(ByteBuffer::create_zeroed(byte_length()));
auto length = export_data(buffer);
m_hash = string_hash(reinterpret_cast<char const*>(buffer.data()), length);
return *m_hash;
}
bool UnsignedBigInteger::operator==(UnsignedBigInteger const& other) const
{
auto length = trimmed_length();
if (length != other.trimmed_length())
return false;
return !__builtin_memcmp(m_words.data(), other.words().data(), length * (BITS_IN_WORD / 8));
return mp_cmp(&m_mp, &other.m_mp) == MP_EQ;
}
bool UnsignedBigInteger::operator!=(UnsignedBigInteger const& other) const
@ -621,26 +346,7 @@ bool UnsignedBigInteger::operator!=(UnsignedBigInteger const& other) const
bool UnsignedBigInteger::operator<(UnsignedBigInteger const& other) const
{
auto length = trimmed_length();
auto other_length = other.trimmed_length();
if (length < other_length) {
return true;
}
if (length > other_length) {
return false;
}
if (length == 0) {
return false;
}
for (int i = length - 1; i >= 0; --i) {
if (m_words[i] == other.m_words[i])
continue;
return m_words[i] < other.m_words[i];
}
return false;
return mp_cmp(&m_mp, &other.m_mp) == MP_LT;
}
bool UnsignedBigInteger::operator<=(UnsignedBigInteger const& other) const
@ -720,21 +426,24 @@ UnsignedBigInteger::CompareResult UnsignedBigInteger::compare_to_double(double v
mantissa_bits |= mantissa_extended_bit;
constexpr u32 bits_in_word = sizeof(u32) * 8;
// Now we shift value to the left virtually, with `exponent - bias` steps
// we then pretend both it and the big int are extended with virtual zeros.
auto next_bigint_word = (BITS_IN_WORD - 1 + bigint_bits_needed) / BITS_IN_WORD;
auto next_bigint_word = (bits_in_word - 1 + bigint_bits_needed) / bits_in_word;
VERIFY(next_bigint_word == trimmed_length());
auto words = this->words();
VERIFY(next_bigint_word == words.size());
auto msb_in_top_word_index = (bigint_bits_needed - 1) % BITS_IN_WORD;
VERIFY(msb_in_top_word_index == (BITS_IN_WORD - count_leading_zeroes(words()[next_bigint_word - 1]) - 1));
auto msb_in_top_word_index = (bigint_bits_needed - 1) % bits_in_word;
VERIFY(msb_in_top_word_index == (bits_in_word - count_leading_zeroes(words[next_bigint_word - 1]) - 1));
// We will keep the bits which are still valid in the mantissa at the top of mantissa bits.
mantissa_bits <<= 64 - (extractor.mantissa_bits + 1);
auto bits_left_in_mantissa = static_cast<size_t>(extractor.mantissa_bits) + 1;
auto get_next_value_bits = [&](size_t num_bits) -> Word {
auto get_next_value_bits = [&](size_t num_bits) -> u32 {
VERIFY(num_bits < 63);
VERIFY(bits_left_in_mantissa > 0);
if (num_bits > bits_left_in_mantissa)
@ -745,24 +454,24 @@ UnsignedBigInteger::CompareResult UnsignedBigInteger::compare_to_double(double v
u64 extracted_bits = mantissa_bits & (((1ull << num_bits) - 1) << (64 - num_bits));
// Now shift the bits down to put the most significant bit on the num_bits position
// this means the rest will be "virtual" zeros.
extracted_bits >>= 32;
extracted_bits >>= bits_in_word;
// Now shift away the used bits and fit the result into a Word.
mantissa_bits <<= num_bits;
VERIFY(extracted_bits <= NumericLimits<Word>::max());
return static_cast<Word>(extracted_bits);
VERIFY(extracted_bits <= NumericLimits<u32>::max());
return static_cast<u32>(extracted_bits);
};
auto bits_in_next_bigint_word = msb_in_top_word_index + 1;
while (next_bigint_word > 0 && bits_left_in_mantissa > 0) {
Word bigint_word = words()[next_bigint_word - 1];
Word double_word = get_next_value_bits(bits_in_next_bigint_word);
u32 bigint_word = words[next_bigint_word - 1];
u32 double_word = get_next_value_bits(bits_in_next_bigint_word);
// For the first bit we have to align it with the top bit of bigint
// and for all the other cases bits_in_next_bigint_word is 32 so this does nothing.
double_word >>= 32 - bits_in_next_bigint_word;
double_word >>= bits_in_word - bits_in_next_bigint_word;
if (bigint_word < double_word)
return CompareResult::DoubleGreaterThanBigInt;
@ -771,14 +480,14 @@ UnsignedBigInteger::CompareResult UnsignedBigInteger::compare_to_double(double v
return CompareResult::DoubleLessThanBigInt;
--next_bigint_word;
bits_in_next_bigint_word = BITS_IN_WORD;
bits_in_next_bigint_word = bits_in_word;
}
// If there are still bits left in bigint than any non zero bit means it has greater value.
if (next_bigint_word > 0) {
VERIFY(bits_left_in_mantissa == 0);
while (next_bigint_word > 0) {
if (words()[next_bigint_word - 1] != 0)
if (words[next_bigint_word - 1] != 0)
return CompareResult::DoubleLessThanBigInt;
--next_bigint_word;
}
@ -797,9 +506,5 @@ UnsignedBigInteger::CompareResult UnsignedBigInteger::compare_to_double(double v
ErrorOr<void> AK::Formatter<Crypto::UnsignedBigInteger>::format(FormatBuilder& fmtbuilder, Crypto::UnsignedBigInteger const& value)
{
StringBuilder builder;
for (int i = value.length() - 1; i >= 0; --i)
TRY(builder.try_appendff("{}|", value.words()[i]));
return Formatter<StringView>::format(fmtbuilder, builder.string_view());
return Formatter<StringView>::format(fmtbuilder, TRY(value.to_base(10)));
}

View file

@ -2,66 +2,44 @@
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
* Copyright (c) 2022, the SerenityOS developers.
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/BigIntBase.h>
#include <AK/ByteString.h>
#include <AK/Concepts.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibCrypto/BigInt/TommathForward.h>
namespace Crypto {
struct UnsignedDivisionResult;
constexpr size_t STARTING_WORD_SIZE = 32;
class UnsignedBigInteger {
public:
using Word = u32;
using StorageSpan = AK::Detail::StorageSpan<Word, false>;
using ConstStorageSpan = AK::Detail::StorageSpan<Word const, false>;
static constexpr size_t BITS_IN_WORD = 32;
// This constructor accepts any unsigned with size up to Word.
template<Integral T>
requires(sizeof(T) <= sizeof(Word))
UnsignedBigInteger(T value)
{
m_words.append(static_cast<Word>(value));
}
explicit UnsignedBigInteger(Vector<Word, STARTING_WORD_SIZE>&& words)
: m_words(move(words))
: UnsignedBigInteger(static_cast<u64>(value))
{
}
UnsignedBigInteger(u8 const* ptr, size_t length);
explicit UnsignedBigInteger(u8 const* ptr, size_t length);
explicit UnsignedBigInteger(Vector<u32> const& words);
explicit UnsignedBigInteger(double value);
explicit UnsignedBigInteger(u64 value);
explicit UnsignedBigInteger(u64 value)
{
static_assert(sizeof(u64) == sizeof(Word) * 2);
m_words.resize_and_keep_capacity(2);
m_words[0] = static_cast<Word>(value & 0xFFFFFFFF);
m_words[1] = static_cast<Word>((value >> 32) & 0xFFFFFFFF);
}
UnsignedBigInteger(UnsignedBigInteger const&);
UnsignedBigInteger& operator=(UnsignedBigInteger const&);
UnsignedBigInteger() = default;
UnsignedBigInteger();
~UnsignedBigInteger();
[[nodiscard]] static UnsignedBigInteger import_data(StringView data) { return import_data((u8 const*)data.characters_without_null_termination(), data.length()); }
[[nodiscard]] static UnsignedBigInteger import_data(u8 const* ptr, size_t length)
{
return UnsignedBigInteger(ptr, length);
}
[[nodiscard]] static UnsignedBigInteger import_data(StringView data) { return import_data(reinterpret_cast<u8 const*>(data.characters_without_null_termination()), data.length()); }
[[nodiscard]] static UnsignedBigInteger import_data(u8 const* ptr, size_t length) { return UnsignedBigInteger(ptr, length); }
size_t export_data(Bytes, bool remove_leading_zeros = false) const;
size_t export_data(Bytes) const;
[[nodiscard]] static ErrorOr<UnsignedBigInteger> from_base(u16 N, StringView str);
[[nodiscard]] ErrorOr<String> to_base(u16 N) const;
@ -77,24 +55,16 @@ public:
[[nodiscard]] double to_double(RoundingMode rounding_mode = RoundingMode::IEEERoundAndTiesToEvenMantissa) const;
[[nodiscard]] Vector<Word, STARTING_WORD_SIZE> const& words() const { return m_words; }
[[nodiscard]] Vector<u32> words() const;
void set_to_0();
void set_to(Word other);
void set_to(u64 other);
void set_to(UnsignedBigInteger const& other);
[[nodiscard]] bool is_zero() const;
[[nodiscard]] bool is_odd() const { return m_words.size() && (m_words[0] & 1); }
[[nodiscard]] bool is_odd() const;
[[nodiscard]] size_t length() const { return m_words.size(); }
// The "trimmed length" is the number of words after trimming leading zeroed words
[[nodiscard]] size_t trimmed_length() const;
[[nodiscard]] size_t byte_length() const { return length() * sizeof(Word); }
[[nodiscard]] size_t trimmed_byte_length() const { return trimmed_length() * sizeof(Word); }
void clamp_to_trimmed_length();
void resize_with_leading_zeros(size_t num_words);
[[nodiscard]] size_t byte_length() const;
size_t one_based_index_of_highest_set_bit() const;
@ -103,22 +73,16 @@ public:
[[nodiscard]] UnsignedBigInteger bitwise_or(UnsignedBigInteger const& other) const;
[[nodiscard]] UnsignedBigInteger bitwise_and(UnsignedBigInteger const& other) const;
[[nodiscard]] UnsignedBigInteger bitwise_xor(UnsignedBigInteger const& other) const;
[[nodiscard]] UnsignedBigInteger bitwise_not_fill_to_one_based_index(size_t) const;
[[nodiscard]] UnsignedBigInteger shift_left(size_t num_bits) const;
[[nodiscard]] ErrorOr<UnsignedBigInteger> bitwise_not_fill_to_one_based_index(size_t) const;
[[nodiscard]] ErrorOr<UnsignedBigInteger> shift_left(size_t num_bits) const;
[[nodiscard]] UnsignedBigInteger shift_right(size_t num_bits) const;
[[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;
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_shift_left(size_t num_bits) const;
[[nodiscard]] u32 hash() const;
void set_bit_inplace(size_t bit_index);
[[nodiscard]] bool operator==(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator!=(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator<(UnsignedBigInteger const& other) const;
@ -135,19 +99,10 @@ public:
[[nodiscard]] CompareResult compare_to_double(double) const;
private:
friend class UnsignedBigIntegerAlgorithms;
friend class SignedBigInteger;
// Little endian
// m_word[0] + m_word[1] * Word::MAX + m_word[2] * Word::MAX * Word::MAX + ...
Vector<Word, STARTING_WORD_SIZE> m_words;
StorageSpan words_span() { return { m_words.data(), m_words.size() }; }
ConstStorageSpan words_span() const
{
return { m_words.data(), m_words.size() };
}
mutable u32 m_cached_hash { 0 };
mutable Optional<size_t> m_cached_trimmed_length;
mp_int m_mp;
mutable Optional<u32> m_hash {};
};
struct UnsignedDivisionResult {

View file

@ -7,13 +7,6 @@ set(SOURCES
ASN1/PEM.cpp
Authentication/HMAC.cpp
BigFraction/BigFraction.cpp
BigInt/Algorithms/BitwiseOperations.cpp
BigInt/Algorithms/Division.cpp
BigInt/Algorithms/GCD.cpp
BigInt/Algorithms/ModularInverse.cpp
BigInt/Algorithms/ModularPower.cpp
BigInt/Algorithms/Multiplication.cpp
BigInt/Algorithms/SimpleOperations.cpp
BigInt/SignedBigInteger.cpp
BigInt/UnsignedBigInteger.cpp
Certificate/Certificate.cpp

View file

@ -21,7 +21,7 @@ public:
RSAPublicKey(Integer n, Integer e)
: m_modulus(move(n))
, m_public_exponent(move(e))
, m_length(m_modulus.trimmed_length() * sizeof(u32))
, m_length(m_modulus.byte_length())
{
}
@ -52,7 +52,7 @@ public:
{
m_modulus = move(n);
m_public_exponent = move(e);
m_length = (m_modulus.trimmed_length() * sizeof(u32));
m_length = m_modulus.byte_length();
}
private:
@ -68,7 +68,7 @@ public:
: m_modulus(move(n))
, m_private_exponent(move(d))
, m_public_exponent(move(e))
, m_length(m_modulus.trimmed_length() * sizeof(u32))
, m_length(m_modulus.byte_length())
{
}
@ -81,7 +81,7 @@ public:
, m_exponent_1(move(dp))
, m_exponent_2(move(dq))
, m_coefficient(move(qinv))
, m_length(m_modulus.trimmed_length() * sizeof(u32))
, m_length(m_modulus.byte_length())
{
}

View file

@ -17,9 +17,6 @@ namespace JS {
GC_DEFINE_ALLOCATOR(BigIntConstructor);
static Crypto::SignedBigInteger const BIGINT_ONE { 1 };
static Crypto::SignedBigInteger const BIGINT_ZERO { 0 };
BigIntConstructor::BigIntConstructor(Realm& realm)
: NativeFunction(realm.vm().names.BigInt.as_string(), realm.intrinsics().function_prototype())
{
@ -75,19 +72,19 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n)
// OPTIMIZATION: mod = bigint (mod 2^0) = 0 < 2^(0-1) = 0.5
if (bits == 0)
return BigInt::create(vm, BIGINT_ZERO);
return BigInt::create(vm, 0);
// 3. Let mod be (bigint) modulo 2^bits.
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
// OPTIMIZATION: mod < 2^(bits-1)
if (mod.is_zero())
return BigInt::create(vm, BIGINT_ZERO);
return BigInt::create(vm, 0);
// 4. If mod ≥ 2^(bits-1), return (mod - 2^bits); ...
if (auto top_bit_index = mod.unsigned_value().one_based_index_of_highest_set_bit(); top_bit_index >= bits) {
// twos complement decode
auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().try_bitwise_not_fill_to_one_based_index(bits)).plus(1);
auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().bitwise_not_fill_to_one_based_index(bits)).plus(1);
return BigInt::create(vm, Crypto::SignedBigInteger { std::move(decoded), true });
}

View file

@ -116,10 +116,9 @@ ErrorOr<String> base64_url_uint_encode(::Crypto::UnsignedBigInteger integer)
// value. Zero is represented as BASE64URL(single zero-valued
// octet), which is "AA".
auto bytes = TRY(ByteBuffer::create_uninitialized(integer.trimmed_byte_length()));
auto bytes = TRY(ByteBuffer::create_uninitialized(integer.byte_length()));
bool const remove_leading_zeroes = true;
auto data_size = integer.export_data(bytes.span(), remove_leading_zeroes);
auto data_size = integer.export_data(bytes.span());
auto data_slice_be = bytes.bytes().slice(bytes.size() - data_size, data_size);
@ -1057,12 +1056,12 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAOAEP::import_key(Web::Crypto::Algorit
// 6. Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA public exponent.
TRY(key->handle().visit(
[&](::Crypto::PK::RSAPublicKey<> const& public_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(public_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(public_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(public_key.public_exponent()));
return {};
},
[&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(private_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(private_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(private_key.public_exponent()));
return {};
},
@ -1635,12 +1634,12 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSAPSS::import_key(AlgorithmParams const
// 6. Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA public exponent.
TRY(key->handle().visit(
[&](::Crypto::PK::RSAPublicKey<> const& public_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(public_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(public_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(public_key.public_exponent()));
return {};
},
[&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(private_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(private_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(private_key.public_exponent()));
return {};
},
@ -2206,12 +2205,12 @@ WebIDL::ExceptionOr<GC::Ref<CryptoKey>> RSASSAPKCS1::import_key(AlgorithmParams
// 6. Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA public exponent.
TRY(key->handle().visit(
[&](::Crypto::PK::RSAPublicKey<> const& public_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(public_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(public_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(public_key.public_exponent()));
return {};
},
[&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> WebIDL::ExceptionOr<void> {
algorithm->set_modulus_length(private_key.modulus().trimmed_byte_length() * 8);
algorithm->set_modulus_length(private_key.modulus().byte_length() * 8);
TRY(algorithm->set_public_exponent(private_key.public_exponent()));
return {};
},

View file

@ -96,10 +96,9 @@ WebIDL::ExceptionOr<void> RsaKeyAlgorithm::set_public_exponent(::Crypto::Unsigne
auto& realm = this->realm();
auto& vm = this->vm();
auto bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(exponent.trimmed_byte_length()));
auto bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(exponent.byte_length()));
bool const remove_leading_zeroes = true;
auto data_size = exponent.export_data(bytes.span(), remove_leading_zeroes);
auto data_size = exponent.export_data(bytes.span());
auto data_slice_be = bytes.bytes().slice(bytes.size() - data_size, data_size);
// The BigInteger typedef from the WebCrypto spec requires the bytes in the Uint8Array be ordered in Big Endian

View file

@ -507,13 +507,13 @@ JS::NativeFunction* create_native_function(JS::VM& vm, Wasm::FunctionAddress add
JS::ThrowCompletionOr<Wasm::Value> to_webassembly_value(JS::VM& vm, JS::Value value, Wasm::ValueType const& type)
{
static ::Crypto::SignedBigInteger two_64 = "1"_sbigint.shift_left(64);
static ::Crypto::SignedBigInteger two_64 = TRY_OR_THROW_OOM(vm, "1"_sbigint.shift_left(64));
switch (type.kind()) {
case Wasm::ValueType::I64: {
auto bigint = TRY(value.to_bigint(vm));
auto value = bigint->big_integer().divided_by(two_64).remainder;
VERIFY(value.unsigned_value().trimmed_length() <= 2);
VERIFY(value.unsigned_value().byte_length() <= sizeof(i64));
i64 integer = static_cast<i64>(value.unsigned_value().to_u64());
if (value.is_negative())
integer = -integer;

View file

@ -14,13 +14,6 @@ shared_library("LibCrypto") {
"Authentication/GHash.cpp",
"Authentication/Poly1305.cpp",
"BigFraction/BigFraction.cpp",
"BigInt/Algorithms/BitwiseOperations.cpp",
"BigInt/Algorithms/Division.cpp",
"BigInt/Algorithms/GCD.cpp",
"BigInt/Algorithms/ModularInverse.cpp",
"BigInt/Algorithms/ModularPower.cpp",
"BigInt/Algorithms/Multiplication.cpp",
"BigInt/Algorithms/SimpleOperations.cpp",
"BigInt/SignedBigInteger.cpp",
"BigInt/UnsignedBigInteger.cpp",
"Checksum/Adler32.cpp",

View file

@ -181,8 +181,8 @@ TEST_CASE(test_encoder_primitives)
roundtrip_value(Crypto::UnsignedBigInteger { 0 });
roundtrip_value(Crypto::UnsignedBigInteger { 1 });
roundtrip_value(Crypto::UnsignedBigInteger { 2 }.shift_left(128));
roundtrip_value(Crypto::UnsignedBigInteger { 2 }.shift_left(256));
roundtrip_value(TRY_OR_FAIL(Crypto::UnsignedBigInteger { 2 }.shift_left(128)));
roundtrip_value(TRY_OR_FAIL(Crypto::UnsignedBigInteger { 2 }.shift_left(256)));
roundtrip_value(Vector { 1, 2, 840, 113549, 1, 1, 1 });
roundtrip_value(Vector { 1, 2, 840, 113549, 1, 1, 11 });

View file

@ -6,7 +6,6 @@
*/
#include <AK/Tuple.h>
#include <LibCrypto/BigInt/Algorithms/UnsignedBigIntegerAlgorithms.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibTest/TestCase.h>
@ -71,57 +70,6 @@ TEST_CASE(test_unsigned_bigint_addition_borrow_with_zero)
EXPECT_EQ(num1.plus(num2).words(), expected_result);
}
TEST_CASE(test_unsigned_bigint_basic_add_to_accumulator)
{
Crypto::UnsignedBigInteger num1(10);
Crypto::UnsignedBigInteger num2(70);
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
EXPECT_EQ(num1.words(), Vector<u32> { 80 });
}
TEST_CASE(test_unsigned_bigint_basic_add_to_empty_accumulator)
{
Crypto::UnsignedBigInteger num1 {};
Crypto::UnsignedBigInteger num2(10);
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
EXPECT_EQ(num1.words(), Vector<u32> { 10 });
}
TEST_CASE(test_unsigned_bigint_basic_add_to_smaller_accumulator)
{
Crypto::UnsignedBigInteger num1(10);
Crypto::UnsignedBigInteger num2({ 10, 10 });
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
Vector<u32> expected_result { 20, 10 };
EXPECT_EQ(num1.words(), expected_result);
}
TEST_CASE(test_unsigned_bigint_add_to_accumulator_with_multiple_carry_levels)
{
Crypto::UnsignedBigInteger num1({ UINT32_MAX - 2, UINT32_MAX });
Crypto::UnsignedBigInteger num2(5);
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
Vector<u32> expected_result { 2, 0, 1 };
EXPECT_EQ(num1.words(), expected_result);
}
TEST_CASE(test_unsigned_bigint_add_to_accumulator_with_leading_zero)
{
Crypto::UnsignedBigInteger num1(1);
Crypto::UnsignedBigInteger num2({ 1, 0 });
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
EXPECT_EQ(num1.words(), Vector<u32> { 2 });
}
TEST_CASE(test_unsigned_bigint_add_to_accumulator_with_carry_and_leading_zero)
{
Crypto::UnsignedBigInteger num1({ UINT32_MAX, 0, 0, 0 });
Crypto::UnsignedBigInteger num2({ 1, 0 });
Crypto::UnsignedBigIntegerAlgorithms::add_into_accumulator_without_allocation(num1, num2);
Vector<u32> expected_result { 0, 1, 0, 0 };
EXPECT_EQ(num1.words(), expected_result);
}
TEST_CASE(test_unsigned_bigint_simple_subtraction)
{
Crypto::UnsignedBigInteger num1(80);
@ -167,14 +115,15 @@ TEST_CASE(test_unsigned_bigint_subtraction_with_large_numbers2)
Crypto::UnsignedBigInteger num2(Vector<u32> { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 });
ErrorOr<Crypto::UnsignedBigInteger> result = num1.minus(num2);
// this test only verifies that we don't crash on an assertion
(void)result;
}
TEST_CASE(test_unsigned_bigint_subtraction_regression_1)
{
auto num = Crypto::UnsignedBigInteger { 1 }.shift_left(256);
auto num = TRY_OR_FAIL(Crypto::UnsignedBigInteger { 1 }.shift_left(256));
Vector<u32> expected_result {
4294967295, 4294967295, 4294967295, 4294967295, 4294967295,
4294967295, 4294967295, 4294967295, 0
4294967295, 4294967295, 4294967295
};
EXPECT_EQ(TRY_OR_FAIL(num.minus(1)).words(), expected_result);
}
@ -266,7 +215,7 @@ TEST_CASE(test_bigint_import_big_endian_decode_encode_roundtrip)
u8 target_buffer[128];
fill_with_random(random_bytes);
auto encoded = Crypto::UnsignedBigInteger::import_data(random_bytes, 128);
encoded.export_data({ target_buffer, 128 });
(void)encoded.export_data({ target_buffer, 128 });
EXPECT(memcmp(target_buffer, random_bytes, 128) == 0);
}
@ -288,16 +237,14 @@ TEST_CASE(test_bigint_big_endian_import)
TEST_CASE(test_bigint_big_endian_export)
{
auto number = "448378203247"_bigint;
char exported[8] { 0 };
auto exported_length = number.export_data({ exported, 8 }, true);
char exported[8] {};
auto exported_length = number.export_data({ exported, 8 });
EXPECT_EQ(exported_length, 5u);
EXPECT(memcmp(exported + 3, "hello", 5) == 0);
EXPECT(memcmp(exported, "hello", 5) == 0);
}
TEST_CASE(test_bigint_one_based_index_of_highest_set_bit)
{
auto num1 = "1234567"_bigint;
auto num2 = "1234567"_bigint;
EXPECT_EQ("0"_bigint.one_based_index_of_highest_set_bit(), 0u);
EXPECT_EQ("1"_bigint.one_based_index_of_highest_set_bit(), 1u);
EXPECT_EQ("7"_bigint.one_based_index_of_highest_set_bit(), 3u);
@ -306,12 +253,12 @@ TEST_CASE(test_bigint_one_based_index_of_highest_set_bit)
TEST_CASE(test_signed_bigint_bitwise_not_fill_to_one_based_index)
{
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(0), "0"_bigint);
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(1), "1"_bigint);
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(2), "3"_bigint);
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(4), "15"_bigint);
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(32), "4294967295"_bigint);
EXPECT_EQ("0"_bigint.bitwise_not_fill_to_one_based_index(33), "8589934591"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(0)), "0"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(1)), "1"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(2)), "3"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(4)), "15"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(32)), "4294967295"_bigint);
EXPECT_EQ(TRY_OR_FAIL("0"_bigint.bitwise_not_fill_to_one_based_index(33)), "8589934591"_bigint);
}
TEST_CASE(test_bigint_bitwise_or)
@ -415,7 +362,7 @@ TEST_CASE(test_bigint_shift_left)
};
for (size_t i = 0; i < tests; ++i)
EXPECT_EQ(num.shift_left(results[i].get<0>()).words(), results[i].get<1>());
EXPECT_EQ(TRY_OR_FAIL(num.shift_left(results[i].get<0>())).words(), results[i].get<1>());
}
TEST_CASE(test_bigint_shift_right)
@ -535,6 +482,7 @@ TEST_CASE(test_signed_subtraction_with_large_numbers_check_for_assertion)
Crypto::SignedBigInteger num2(Crypto::UnsignedBigInteger { Vector<u32> { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 } });
Crypto::SignedBigInteger result = num1.minus(num2);
// this test only verifies that we don't crash on an assertion
(void)result;
}
TEST_CASE(test_signed_multiplication_with_negative_number)