Sfoglia il codice sorgente

LibCrypto: Add UnsignedBigInteger division

The division operation returns both the quotient and the remainder.
Itamar 5 anni fa
parent
commit
0d2777752e

+ 49 - 5
Libraries/LibCrypto/BigInt/UnsignedBigInteger.cpp

@@ -75,12 +75,12 @@ UnsignedBigInteger UnsignedBigInteger::sub(const UnsignedBigInteger& other) cons
     UnsignedBigInteger result;
 
     if (*this < other) {
-        dbg() << "WARNING: bigint subtraction creates a negative number!";
         return UnsignedBigInteger::create_invalid();
     }
 
     u8 borrow = 0;
     for (size_t i = 0; i < other.length(); ++i) {
+        // This assertion should not fail, because we verified that *this>other at the beginning of the function
         ASSERT(!(borrow == 1 && m_words[i] == 0));
 
         if (m_words[i] - borrow < other.m_words[i]) {
@@ -128,6 +128,47 @@ UnsignedBigInteger UnsignedBigInteger::multiply(const UnsignedBigInteger& other)
     return result;
 }
 
+/**
+ * Complexity: O(N^2) where N is the number of words in the larger number
+ * Division method:
+ * We loop over the bits of the divisor, attempting to subtract divisor<<i from the dividend.
+ * If the result is non-negative, it means that divisor*2^i "fits" in the dividend,
+ * so we set the ith bit in the quotient and reduce divisor<<i from the dividend.
+ * When we're done, what's left from the dividend is the remainder.
+ */
+UnsignedDivisionResult UnsignedBigInteger::divide(const UnsignedBigInteger& divisor) const
+{
+    UnsignedBigInteger leftover_dividend(*this);
+    UnsignedBigInteger quotient;
+
+    // iterate all bits
+    for (int word_index = trimmed_length() - 1; word_index >= 0; --word_index) {
+        for (int bit_index = UnsignedBigInteger::BITS_IN_WORD - 1; bit_index >= 0; --bit_index) {
+
+            const size_t shift_amount = word_index * UnsignedBigInteger::BITS_IN_WORD + bit_index;
+            UnsignedBigInteger divisor_shifted = divisor.shift_left(shift_amount);
+
+            UnsignedBigInteger temp_subtraction_result = leftover_dividend.sub(divisor_shifted);
+            if (!temp_subtraction_result.is_invalid()) {
+                leftover_dividend = temp_subtraction_result;
+                quotient.set_bit_inplace(shift_amount);
+            }
+        }
+    }
+    return UnsignedDivisionResult { quotient, leftover_dividend };
+}
+
+void UnsignedBigInteger::set_bit_inplace(size_t bit_index)
+{
+    const size_t word_index = bit_index / UnsignedBigInteger::BITS_IN_WORD;
+    const size_t inner_word_index = bit_index % UnsignedBigInteger::BITS_IN_WORD;
+
+    for (size_t i = length(); i <= word_index; ++i) {
+        m_words.append(0);
+    }
+    m_words[word_index] |= (1 << inner_word_index);
+}
+
 UnsignedBigInteger UnsignedBigInteger::shift_left(size_t num_bits) const
 {
     // We can only do shift operations on individual words
@@ -213,12 +254,16 @@ bool UnsignedBigInteger::operator<(const UnsignedBigInteger& other) const
         return false;
     }
 
-    size_t length = trimmed_length();
+    int length = trimmed_length();
     if (length == 0) {
         return false;
     }
-
-    return m_words[length - 1] < other.m_words[length - 1];
+    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;
 }
 
 size_t UnsignedBigInteger::trimmed_length() const
@@ -237,5 +282,4 @@ UnsignedBigInteger UnsignedBigInteger::create_invalid()
     invalid.invalidate();
     return invalid;
 }
-
 }

+ 13 - 1
Libraries/LibCrypto/BigInt/UnsignedBigInteger.h

@@ -30,6 +30,9 @@
 #include <AK/Vector.h>
 
 namespace Crypto {
+
+struct UnsignedDivisionResult;
+
 class UnsignedBigInteger {
 public:
     UnsignedBigInteger(u32 x) { m_words.append(x); }
@@ -49,7 +52,10 @@ public:
     UnsignedBigInteger sub(const UnsignedBigInteger& other) const;
     UnsignedBigInteger multiply(const UnsignedBigInteger& other) const;
     UnsignedBigInteger shift_left(size_t num_bits) const;
-    UnsignedBigInteger shift_left_by_n_words(const size_t number_of_words) const;
+
+    UnsignedDivisionResult divide(const UnsignedBigInteger& divisor) const;
+
+    void set_bit_inplace(size_t bit_index);
 
     size_t length() const { return m_words.size(); }
 
@@ -63,6 +69,7 @@ public:
     bool is_invalid() const { return m_is_invalid; }
 
 private:
+    UnsignedBigInteger shift_left_by_n_words(const size_t number_of_words) const;
     u32 shift_left_get_one_word(const size_t num_bits, const size_t result_word_index) const;
 
     static constexpr size_t BITS_IN_WORD = 32;
@@ -72,6 +79,11 @@ private:
     bool m_is_invalid { false };
 };
 
+struct UnsignedDivisionResult {
+    Crypto::UnsignedBigInteger quotient;
+    Crypto::UnsignedBigInteger remainder;
+};
+
 }
 
 inline const LogStream& operator<<(const LogStream& stream, const Crypto::UnsignedBigInteger value)

+ 51 - 0
Userland/test-crypto.cpp

@@ -306,6 +306,7 @@ void bigint_test_fibo500();
 void bigint_addition_edgecases();
 void bigint_subtraction();
 void bigint_multiplication();
+void bigint_division();
 
 int aes_cbc_tests()
 {
@@ -801,6 +802,7 @@ int bigint_tests()
     bigint_addition_edgecases();
     bigint_subtraction();
     bigint_multiplication();
+    bigint_division();
     return 0;
 }
 
@@ -905,6 +907,14 @@ void bigint_subtraction()
             FAIL(Incorrect Result);
         }
     }
+    {
+        I_TEST((BigInteger | Subtraction with large numbers 2));
+        Crypto::UnsignedBigInteger num1(Vector<u32> { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 });
+        Crypto::UnsignedBigInteger num2(Vector<u32> { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 });
+        Crypto::UnsignedBigInteger result = num1.sub(num2);
+        // this test only verifies that we don't crash on an assertion
+        PASS;
+    }
 }
 
 void bigint_multiplication()
@@ -944,3 +954,44 @@ void bigint_multiplication()
         }
     }
 }
+void bigint_division()
+{
+    {
+        I_TEST((BigInteger | Simple Division));
+        Crypto::UnsignedBigInteger num1(27194);
+        Crypto::UnsignedBigInteger num2(251);
+        auto result = num1.divide(num2);
+        Crypto::UnsignedDivisionResult expected = { Crypto::UnsignedBigInteger(108), Crypto::UnsignedBigInteger(86) };
+        if (result.quotient == expected.quotient && result.remainder == expected.remainder) {
+            PASS;
+        } else {
+            FAIL(Incorrect Result);
+        }
+    }
+    {
+        I_TEST((BigInteger | Division with big numbers));
+        Crypto::UnsignedBigInteger num1 = bigint_fibonacci(386);
+        Crypto::UnsignedBigInteger num2 = bigint_fibonacci(238);
+        auto result = num1.divide(num2);
+        Crypto::UnsignedDivisionResult expected = {
+            Crypto::UnsignedBigInteger(Vector<u32> { 2300984486, 2637503534, 2022805584, 107 }),
+            Crypto::UnsignedBigInteger(Vector<u32> { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 })
+        };
+        if (result.quotient == expected.quotient && result.remainder == expected.remainder) {
+            PASS;
+        } else {
+            FAIL(Incorrect Result);
+        }
+    }
+    {
+        I_TEST((BigInteger | Combined test));
+        auto num1 = bigint_fibonacci(497);
+        auto num2 = bigint_fibonacci(238);
+        auto div_result = num1.divide(num2);
+        if (div_result.quotient.multiply(num2).add(div_result.remainder) == num1) {
+            PASS;
+        } else {
+            FAIL(Incorrect Result);
+        }
+    }
+}