diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f758c35 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: CI/CD Pipeline + +on: + pull_request: + branches: [ "main" ] + push: + branches: [ "main" ] + +jobs: + # JOB 1: Build and test matrix + # Runs on PRs and pushes to main across OS's + build-and-test: + name: Build & Test (${{ matrix.config.name }} - ${{ matrix.build-configuration }}) + runs-on: ${{ matrix.config.os }} + strategy: + fail-fast: false + matrix: + config: + - { name: "Windows gcc", os: windows-latest, cc: "gcc", cxx: "g++" } + - { name: "Ubuntu gcc", os: ubuntu-latest, cc: "gcc", cxx: "g++" } + - { name: "MacOS clang", os: macos-latest, cc: "clang", cxx: "clang++" } + build-configuration: [ Debug, Release ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Ninja + uses: ashutoshvarma/setup-ninja@master + + - name: Configure CMake + run: | + cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} -DCMAKE_C_COMPILER=${{ matrix.config.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} + + - name: Build Project + run: cmake --build build --config ${{ matrix.build-configuration }} + + - name: Run Catch2 Unit Tests + # Runs the unit tests you specified + run: ctest --test-dir build -C ${{ matrix.build-configuration }} --output-on-failure + + # We only package and upload 'Release' builds to save storage and time + - name: Compress Release Build Directory + if: matrix.build-configuration == 'Release' + uses: thedoctor0/zip-release@0.7.5 + with: + type: 'zip' + path: 'build' + filename: '${{ matrix.config.os }}-build.zip' + + - name: Upload Artifacts for Release + if: matrix.build-configuration == 'Release' + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.config.os }}-build + path: ${{ matrix.config.os }}-build.zip + retention-days: 1 + + + # JOB 2: Create Pre-Release + # Runs only on pushes to main, after tests pass + release: + name: Create Pre-Release + needs: build-and-test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Download all OS Artifacts + uses: actions/download-artifact@v4 + with: + path: ./release-artifacts + merge-multiple: true # Puts the Mac, Win, and Linux zip files in the same folder + + - name: Generate Tag & Publish Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Create naming convention + DATE=$(date +'%Y%m%d') + SHORT_SHA=${GITHUB_SHA::8} + TAG_NAME="dev-${DATE}-${SHORT_SHA}" + + # Create Pre-Release and attach all downloaded .zip files using modern GitHub CLI + gh release create "$TAG_NAME" ./release-artifacts/*.zip \ + --title "Development Build $TAG_NAME" \ + --prerelease \ + --generate-notes \ No newline at end of file diff --git a/.github/workflows/clang-format-dispatch.yml b/.github/workflows/clang-format-dispatch.yml new file mode 100644 index 0000000..05083d3 --- /dev/null +++ b/.github/workflows/clang-format-dispatch.yml @@ -0,0 +1,34 @@ +# This workflow should only be run by administrators + +name: ClangFormat correction + +on: + workflow_dispatch: + +jobs: + ClangFormat: + name: ClangFormat correction + runs-on: ubuntu-22.04 + + # Give the default GITHUB_TOKEN write permission to commit and push the changed files back to the repository. + permissions: + contents: write + + steps: + # Clone Repo + - name: Checkout + uses: actions/checkout@v4 + + - name: Install clang-format + run: sudo apt-get update && sudo apt-get install -y clang-format + + - name: Format code + #run: clang-format -i **/*.cpp + run: find . -type f \( -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -exec clang-format -i {} \; + + # Commit all changed files back to the repository + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: 'Apply Clang formatting' + create_branch: false \ No newline at end of file diff --git a/.github/workflows/clang-format-pr.yml b/.github/workflows/clang-format-pr.yml new file mode 100644 index 0000000..085384f --- /dev/null +++ b/.github/workflows/clang-format-pr.yml @@ -0,0 +1,34 @@ +# This workflow is from https://github.com/official-stockfish/Stockfish/blob/master/.github/workflows/clang-format.yml#L23 + +# This workflow will run clang-format and comment on the PR. +# Because of security reasons, it is crucial that this workflow +# executes no shell script nor runs make. +# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + +name: ClangFormat check +on: + pull_request_target: + branches: + - "main" + paths: + - "**.cpp" + - "**.h" + +# needed for automatic comment in pr +permissions: + pull-requests: write + +jobs: + Clang-Format: + name: Clang-Format + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Run clang-format style check + uses: jidicula/clang-format-action@v4.15.0 + id: clang-format + with: + clang-format-version: "20" \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4705bee..9e1149c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.28.3) -project(Base256) +project(BigInt) # Enforce C++20 and disable compiler-specific extensions set(CMAKE_CXX_STANDARD 20) diff --git a/README.md b/README.md index 41d5383..c2fe6d0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Base256 +# BigInt -Base256 is a small C++20 library for unsigned big-integer arithmetic using -Base-256 byte storage. It was developed for use in +BigInt is a small C++20 library for unsigned big-integer arithmetic using +64-bit limb storage. It was developed for use in [RSA-Encryptor](https://github.com/ParallelEngineering/RSA-Encryptor) and is intended to be integrated as a Git submodule. @@ -11,43 +11,45 @@ The library provides arithmetic for numbers that can grow beyond native integer limits such as `uint64_t`. This makes it useful for cryptographic-style calculations where values often need more than the CPU's built-in integer width. -## How Base256 works +## How BigInt works -`operations::Base256` stores a number as a dynamically sized `ByteArray` -(`std::vector`). Each byte is one Base-256 digit: +`operations::BigInt` stores a number as a dynamically sized `ByteArray` +(`std::vector`). Each limb is one base-2^64 digit: -- index `0` contains the least-significant byte -- index `1` contains the next `256^1` byte +- index `0` contains the least-significant limb +- index `1` contains the next `(2^64)^1` limb - higher indexes continue the same little-endian layout -After arithmetic operations, the internal byte array is normalized by removing -unused high zero-bytes while keeping zero represented as a single byte. +After arithmetic operations, the internal limb array is normalized by removing +unused high zero-limbs while keeping zero represented as a single limb. For a deeper explanation of the internal representation, see -[`docs/base256.md`](docs/base256.md). +[`docs/bigint.md`](docs/bigint.md). ## Usage ```cpp -#include "base256.h" +#include "bigint.h" +#include "math_utils.h" -using operations::Base256; +using operations::BigInt; +using operations::math::pow; int main() { - Base256 a(4294967295ULL); - Base256 b(2); + BigInt a(4294967295ULL); + BigInt b(2); - Base256 sum = a + b; - Base256 product = a * b; - Base256 remainder = product % b; - Base256 power = Base256::pow(b, 16); + BigInt sum = a + b; + BigInt product = a * b; + BigInt remainder = product % b; + BigInt power = pow(b, 16); power.print(); } ``` The class supports construction from `uint64_t`, arithmetic operators -`+`, `-`, `*`, `/`, `%`, compound assignments, comparisons, `Base256::pow`, and +`+`, `-`, `*`, `/`, `%`, compound assignments, comparisons, `operations::math::pow`, and decimal output with `print()`. ## Integration @@ -55,17 +57,17 @@ decimal output with `print()`. Add this repository to your parent project and include it with CMake: ```cmake -add_subdirectory(lib/Base256) -target_link_libraries(YourTarget PRIVATE Base256) +add_subdirectory(lib/BigInt) +target_link_libraries(YourTarget PRIVATE BigInt) ``` If your parent project does not already expose the library headers, add the submodule's `src` directory to your include path. > [!NOTE] -> Base256 is a library, not a standalone executable. The library code itself is +> BigInt is a library, not a standalone executable. The library code itself is > meant to be used from another program. The tests, however, can be built and run -> directly from this repository without integrating Base256 into another project. +> directly from this repository without integrating BigInt into another project. ## Tests @@ -76,5 +78,3 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug cmake --build build ctest --test-dir build --output-on-failure ``` - - diff --git a/docs/base256.md b/docs/base256.md deleted file mode 100644 index 6c2f3fa..0000000 --- a/docs/base256.md +++ /dev/null @@ -1,74 +0,0 @@ -# Base256: Custom Big-Integer Mathematics - -The **`Base256`** class is a dynamically sized, arbitrary-precision integer implementation. It is designed to handle infinitely large unsigned integers by utilizing dynamically expanding contiguous memory (`std::vector`). - -By operating entirely in software, `Base256` bypasses hardware ALU constraints (like standard 64-bit boundaries), making it suitable for cryptographic computations where numbers routinely span thousands of bits. - ---- - -## Technical Architecture - -### 1. The Radix-256 (Base-256) System -Hardware natively computes in Base-2 (Binary), while human-readable formats use Base-10 (Decimal). This class uses **Base-256**, treating exactly **one byte** (`uint8_t`) as a single, discrete "digit." - -Each digit holds a value from `0` to `255`. If an arithmetic operation pushes a byte beyond 255, it overflows and carries a `1` into the next byte magnitude. The total mathematical value is represented by the polynomial: - -`Value = d[0]*(256^0) + d[1]*(256^1) + d[2]*(256^2) ... + d[n]*(256^n)` - -### 2. Little-Endian Memory Mapping -Numbers are stored in **little-endian** order, meaning the least-significant byte (LSB) is stored at index `0` of the vector. -Or in other words 256^0 is always stored at `data[0]`. - -This architecture guarantees that the array index maps perfectly to the radix exponent. The byte at `data[i]` is strictly bound to `256^i`. This eliminates the need for complex index inversion during iterative algorithms. - -| Number (Base 10) | Hexadecimal | Base-256 Array `vector` | Radix Evaluation | -|:-----------------|:------------|:---------------------------------|:----------------------------------| -| `42` | `0x2A` | `[42]` | 42 * 256^0 | -| `258` | `0x0102` | `[2, 1]` | 2 * 256^0 + 1 * 256^1 | -| `65536` | `0x010000` | `[0, 0, 1]` | 0 * 256^0 + 0 * 256^1 + 1 * 256^2 | - -### 3. Vector Normalization -Because operations like subtraction and division can shrink the magnitude of a number, mathematical leading zeros appear as trailing elements at the end of the little-endian vector. - -The class enforces strict **Array Normalization**. After every operation, trailing zero-bytes are instantly popped from the vector until only the true magnitude remains (or until a single `[0]` is left). This ensures `data.size()` operates as a reliable magnitude heuristic and prevents logic failure during bitwise comparisons. - ---- - -## Algorithmic Mechanics - -- [Addition](algorithms/add.md#addition) -- [Subtraction](algorithms/sub.md#subtraction) -- [Subtraction](algorithms/mul.md#multiplication) -- [Subtraction](algorithms/div.md#division) ---- - -## Code Examples & Usage - -The class acts identically to standard primitive integers, natively supporting cross-byte cascading, memory-safe aliasing, and underflow clamping. - -```cpp -#include "vec/operations.h" -using operations::Base256; - -// 1. Initialization -Base256 a(4294967295); // Initializes from max 32-bit int -Base256 b(2); - -// 2. Infinite Precision Arithmetic -Base256 sum = a + b; // Handles byte-overflows natively -Base256 prod = a * b; // Expands underlying vector memory dynamically -Base256 quot = a / b; // Evaluates using bitwise long-division - -// 3. Safe Compound Assignment & Aliasing -a += b; -a *= a; // Memory-safe aliasing (reads/writes safely isolated) - -// 4. Edge-Case Safety -Base256 zero = b - a; // Negative subtraction cleanly clamps to 0 -Base256 drop = b / a; // Fractional division drops remainder (evaluates to 0) - -// 5. Comparisons & Output -if (sum > a && prod != zero) { - sum.print(); // Iterates in reverse to print standard Big-Endian output -} -``` \ No newline at end of file diff --git a/docs/bigint.md b/docs/bigint.md new file mode 100644 index 0000000..7bfd560 --- /dev/null +++ b/docs/bigint.md @@ -0,0 +1,73 @@ +# BigInt: Custom Big-Integer Mathematics + +The **`BigInt`** class is a dynamically sized, arbitrary-precision integer implementation. It is designed to handle infinitely large unsigned integers by utilizing dynamically expanding contiguous memory (`std::vector`). + +By operating entirely in software, `BigInt` bypasses hardware ALU constraints (like standard 64-bit boundaries), making it suitable for cryptographic computations where numbers routinely span thousands of bits. + +--- + +## Technical Architecture + +### 1. The 64-Bit Limb System +Hardware natively computes in Base-2 (Binary), while human-readable formats use Base-10 (Decimal). This class uses a base-2^64 representation, treating exactly one `uint64_t` limb as a single, discrete digit. + +Each digit holds a value from `0` to `2^64 - 1`. If an arithmetic operation pushes a limb beyond that range, it overflows and carries into the next limb. The total mathematical value is represented by the polynomial: + +`Value = d[0]*((2^64)^0) + d[1]*((2^64)^1) + d[2]*((2^64)^2) ... + d[n]*((2^64)^n)` + +### 2. Little-Endian Memory Mapping +Numbers are stored in **little-endian** order, meaning the least-significant limb is stored at index `0` of the vector. +Or in other words `(2^64)^0` is always stored at `data[0]`. + +This architecture guarantees that the array index maps perfectly to the radix exponent. The limb at `data[i]` is strictly bound to `(2^64)^i`. This eliminates the need for complex index inversion during iterative algorithms. + +| Number (Base 10) | Hexadecimal | Limb Array `vector` | Radix Evaluation | +|:-----------------|:-----------------------|:------------------------------|:-----------------------------------------| +| `42` | `0x2A` | `[42]` | 42 * (2^64)^0 | +| `2^64` | `0x010000000000000000` | `[0, 1]` | 0 * (2^64)^0 + 1 * (2^64)^1 | +| `2^64 + 7` | `0x010000000000000007` | `[7, 1]` | 7 * (2^64)^0 + 1 * (2^64)^1 | + +### 3. Vector Normalization +Because operations like subtraction and division can shrink the magnitude of a number, mathematical leading zeros appear as trailing elements at the end of the little-endian vector. + +The class enforces strict **Array Normalization**. After every operation, trailing zero-limbs are instantly popped from the vector until only the true magnitude remains (or until a single `[0]` is left). This ensures `data.size()` operates as a reliable magnitude heuristic and prevents logic failure during bitwise comparisons. + +--- + +## Algorithmic Mechanics + +The implementation provides addition, subtraction, multiplication, division, modulo, comparisons, decimal output, and selected helper functions in `operations::math`. + +--- + +## Code Examples & Usage + +The class acts like an unsigned primitive integer, natively supporting cross-limb cascading, memory-safe aliasing, and underflow clamping. + +```cpp +#include "bigint.h" + +using operations::BigInt; + +// 1. Initialization +BigInt a(4294967295); // Initializes from max 32-bit int +BigInt b(2); + +// 2. Infinite Precision Arithmetic +BigInt sum = a + b; // Handles limb-overflows natively +BigInt prod = a * b; // Expands underlying vector memory dynamically +BigInt quot = a / b; // Evaluates using bitwise long-division + +// 3. Safe Compound Assignment & Aliasing +a += b; +a *= a; // Memory-safe aliasing (reads/writes safely isolated) + +// 4. Edge-Case Safety +BigInt zero = b - a; // Negative subtraction cleanly clamps to 0 +BigInt drop = b / a; // Fractional division drops remainder (evaluates to 0) + +// 5. Comparisons & Output +if (sum > a && prod != zero) { + sum.print(); // Iterates in reverse to print standard big-endian output +} +``` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1930ef0..b27906f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,10 +1,10 @@ -add_library(Base256 - base256.cpp +add_library(BigInt + bigint.cpp math_utils.cpp ) # Add src directory to path for header includes -target_include_directories(Base256 +target_include_directories(BigInt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) \ No newline at end of file diff --git a/src/base256.cpp b/src/bigint.cpp similarity index 93% rename from src/base256.cpp rename to src/bigint.cpp index f6837d6..0ad2b73 100644 --- a/src/base256.cpp +++ b/src/bigint.cpp @@ -1,8 +1,8 @@ -#include "base256.h" +#include "bigint.h" #include -void operations::Base256::add(const ByteArray &b) noexcept { +void operations::BigInt::add(const ByteArray &b) noexcept { ByteArray result; // Get the max iterations based on the largest vector @@ -44,7 +44,7 @@ void operations::Base256::add(const ByteArray &b) noexcept { data = std::move(result); } -[[nodiscard]] ByteArray operations::Base256::sub(const ByteArray &a, const ByteArray &b) noexcept { +[[nodiscard]] ByteArray operations::BigInt::sub(const ByteArray &a, const ByteArray &b) noexcept { // Safely clamp to 0 if the number being subtracted is larger than the base if (isBigger(b, a)) { return {0}; @@ -81,7 +81,7 @@ void operations::Base256::add(const ByteArray &b) noexcept { return result; } -void operations::Base256::subInPlace(ByteArray &a, const ByteArray &b) { +void operations::BigInt::subInPlace(ByteArray &a, const ByteArray &b) { // Safely clamp to 0 if the number being subtracted is larger than the base if (isBigger(b, a)) { a = {0}; @@ -115,12 +115,12 @@ void operations::Base256::subInPlace(ByteArray &a, const ByteArray &b) { } // The return value can only be positive, if it would be negative, 0 is returned -void operations::Base256::sub(const ByteArray &b) noexcept { +void operations::BigInt::sub(const ByteArray &b) noexcept { ByteArray result = sub(data, b); data = std::move(result); } -void operations::Base256::mul(const ByteArray &b) noexcept { +void operations::BigInt::mul(const ByteArray &b) noexcept { if (data.empty() || b.empty()) { data.clear(); return; @@ -198,7 +198,7 @@ void operations::Base256::mul(const ByteArray &b) noexcept { data = std::move(result); } -void operations::Base256::div(const ByteArray &divisor, ByteArray *remaining) noexcept { +void operations::BigInt::div(const ByteArray &divisor, ByteArray *remaining) noexcept { const std::int64_t initialDividendIndex = getStartBitIndex(data); if (isZero(divisor) || initialDividendIndex == INVALID_START_BIT_INDEX) { if (remaining != nullptr) *remaining = {0}; diff --git a/src/base256.h b/src/bigint.h similarity index 63% rename from src/base256.h rename to src/bigint.h index c00220d..f11b011 100644 --- a/src/base256.h +++ b/src/bigint.h @@ -1,5 +1,5 @@ -#ifndef VEC_OPERATIONS_H -#define VEC_OPERATIONS_H +#ifndef BIGINT_H +#define BIGINT_H #include #include @@ -9,15 +9,15 @@ #include "helper.h" namespace operations { - class Base256 { + class BigInt { public: - Base256(const std::uint64_t initialValue) { data = convertToVector(initialValue); } + BigInt(const std::uint64_t initialValue) { data = convertToVector(initialValue); } - Base256(const Base256 &base256) { data = base256.data; } + BigInt(const BigInt &bigint) { data = bigint.data; } - Base256(ByteArray bytes) : data(std::move(bytes)) { } + BigInt(ByteArray bytes) : data(std::move(bytes)) { } - Base256() { data = convertToVector(0); } + BigInt() { data = convertToVector(0); } [[nodiscard]] const ByteArray &getBytes() const { return data; } @@ -66,7 +66,7 @@ namespace operations { } } - Base256 &operator=(const Base256 &other) { + BigInt &operator=(const BigInt &other) { // We protect us from self assignment if (this != &other) { data = other.data; @@ -74,84 +74,84 @@ namespace operations { return *this; } - friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); + friend BigInt operator+(const BigInt &rhs, const BigInt &lhs) { + BigInt result(rhs); result += lhs; return result; } - Base256 &operator+=(const Base256 &rhs) { + BigInt &operator+=(const BigInt &rhs) { add(rhs.data); return *this; } - friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); + friend BigInt operator-(const BigInt &rhs, const BigInt &lhs) { + BigInt result(rhs); result -= lhs; return result; } - Base256 &operator-=(const Base256 &rhs) { + BigInt &operator-=(const BigInt &rhs) { sub(rhs.data); return *this; } - friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); + friend BigInt operator*(const BigInt &rhs, const BigInt &lhs) { + BigInt result(rhs); result *= lhs; return result; } - Base256 &operator*=(const Base256 &rhs) { + BigInt &operator*=(const BigInt &rhs) { mul(rhs.data); return *this; } - friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); + friend BigInt operator/(const BigInt &rhs, const BigInt &lhs) { + BigInt result(rhs); result /= lhs; return result; } - Base256 &operator/=(const Base256 &rhs) { + BigInt &operator/=(const BigInt &rhs) { div(rhs.data); return *this; } - friend Base256 operator%(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); + friend BigInt operator%(const BigInt &rhs, const BigInt &lhs) { + BigInt result(rhs); result %= lhs; return result; } - Base256 &operator%=(const Base256 &rhs) { - Base256 remaining; + BigInt &operator%=(const BigInt &rhs) { + BigInt remaining; this->div(rhs.data, &remaining.data); this->data = std::move(remaining.data); return *this; } - [[nodiscard]] friend bool operator==(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator==(const BigInt &lhs, const BigInt &rhs) { return isEqual(lhs.data, rhs.data); } - [[nodiscard]] friend bool operator!=(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator!=(const BigInt &lhs, const BigInt &rhs) { return !isEqual(lhs.data, rhs.data); } - [[nodiscard]] friend bool operator>(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator>(const BigInt &lhs, const BigInt &rhs) { return isBigger(lhs.data, rhs.data); } - [[nodiscard]] friend bool operator<(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator<(const BigInt &lhs, const BigInt &rhs) { return isBigger(rhs.data, lhs.data); } - [[nodiscard]] friend bool operator>=(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator>=(const BigInt &lhs, const BigInt &rhs) { return !isBigger(rhs.data, lhs.data); } - [[nodiscard]] friend bool operator<=(const Base256 &lhs, const Base256 &rhs) { + [[nodiscard]] friend bool operator<=(const BigInt &lhs, const BigInt &rhs) { return !isBigger(lhs.data, rhs.data); } diff --git a/src/helper.h b/src/helper.h index 102fd2b..0e354f5 100644 --- a/src/helper.h +++ b/src/helper.h @@ -1,5 +1,5 @@ -#ifndef BASE_256_HELPER_H -#define BASE_256_HELPER_H +#ifndef BIGINT_HELPER_H +#define BIGINT_HELPER_H #include @@ -15,15 +15,15 @@ } // Because of normalization, the highest bit is guaranteed - // to be in the very last byte of the vector. - const std::uint64_t highestByteIndex = a.size() - 1; - const std::uint64_t highestByte = a.back(); + // to be in the very last limb of the vector. + const std::uint64_t highestLimbIndex = a.size() - 1; + const std::uint64_t highestLimb = a.back(); - // Find the highest bit in just this one byte (max 64 iterations) + // Find the highest bit in just this one limb (max 64 iterations) for (std::int64_t bit = 63; bit >= 0; bit--) { - if ((highestByte & (0b1ULL << bit)) != 0) { - // Calculate total bit index: (Byte Position * 8) + Bit Position - return (highestByteIndex * 64) + bit; + if ((highestLimb & (0b1ULL << bit)) != 0) { + // Calculate total bit index: (limb position * 64) + bit position + return (highestLimbIndex * 64) + bit; } } @@ -34,7 +34,7 @@ [[nodiscard]] inline ByteArray convertToVector(const std::uint64_t number) noexcept { ByteArray result; - // Ensure 0 results in at least [0], never[] + // Ensure 0 results in at least [0], never [] if (number == 0) return {0}; result.push_back(number); return result; @@ -58,11 +58,11 @@ bool mostSignificantBit = (sourceNumber[sourceNumberIndex] & sourceNumberMask) != 0; - for (const std::uint64_t currentByte : numberToShift) { - // The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets + for (const std::uint64_t currentLimb : numberToShift) { + // Evaluate the MSB before the limb gets // overwritten/shifted. - const bool nextMSB = (currentByte & mask) != 0; - result.push_back(((currentByte << 1) | mostSignificantBit)); + const bool nextMSB = (currentLimb & mask) != 0; + result.push_back(((currentLimb << 1) | mostSignificantBit)); mostSignificantBit = nextMSB; } @@ -80,20 +80,19 @@ inline void addBitFromNumberInPlace(ByteArray &numberToShift, return; } - // bitIndex & 7 is equivalent to bitIndex % 8 + // bitIndex & 63 is equivalent to bitIndex % 64 const std::uint64_t sourceNumberMask = 0b1ULL << (bitIndex & 63); constexpr std::uint64_t mask = 0x8000'0000'0000'0000UL; - // bitIndex >> 3 is equivalent to bitIndex / 8 const std::uint64_t sourceNumberIndex = bitIndex / 64; bool mostSignificantBit = (sourceNumber[sourceNumberIndex] & sourceNumberMask) != 0; for (std::uint64_t &i : numberToShift) { - // The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets + // Evaluate the MSB before the limb gets // overwritten/shifted. - const std::uint64_t currentByte = i; - const bool nextMSB = (currentByte & mask) != 0; - i = (currentByte << 1) | mostSignificantBit; + const std::uint64_t currentLimb = i; + const bool nextMSB = (currentLimb & mask) != 0; + i = (currentLimb << 1) | mostSignificantBit; mostSignificantBit = nextMSB; } @@ -149,4 +148,4 @@ inline void addBitFromNumberInPlace(ByteArray &numberToShift, return true; } -#endif // BASE_256_HELPER_H +#endif // BIGINT_HELPER_H diff --git a/src/math_utils.cpp b/src/math_utils.cpp index 09037e1..0d177c8 100644 --- a/src/math_utils.cpp +++ b/src/math_utils.cpp @@ -5,53 +5,53 @@ namespace operations::math { namespace { - // Helper function to shift a Base256 number right by n bytes (dividing by 256^n) - Base256 shiftRightBytes(const Base256 &val, size_t n) { - const auto &bytes = val.getBytes(); - if (n >= bytes.size()) { - return Base256(0); + // Shift a BigInt right by n limbs, dividing by (2^64)^n. + BigInt shiftRightLimbs(const BigInt &val, size_t n) { + const auto &limbs = val.getBytes(); + if (n >= limbs.size()) { + return BigInt(0); } - ByteArray newBytes(bytes.begin() + n, bytes.end()); - return Base256(newBytes); + ByteArray newLimbs(limbs.begin() + n, limbs.end()); + return BigInt(newLimbs); } // Barrett Reducer encapsulation to replace slow divisions struct BarrettReducer { - Base256 modulus; + BigInt modulus; size_t k; - Base256 mu; + BigInt mu; - BarrettReducer(const Base256 &mod) : modulus(mod) { + BarrettReducer(const BigInt &mod) : modulus(mod) { k = modulus.getBytes().size(); - // Create 256^(2k) as [0, 0, ..., 1] in little-endian representation - ByteArray tempBytes(2 * k + 1, 0); - tempBytes.back() = 1; - Base256 bigPower(tempBytes); + // Create (2^64)^(2k) as [0, 0, ..., 1] in little-endian representation. + ByteArray tempLimbs(2 * k + 1, 0); + tempLimbs.back() = 1; + BigInt bigPower(tempLimbs); - // Compute mu = floor(256^(2k) / modulus) + // Compute mu = floor((2^64)^(2k) / modulus). // This is the only division performed during the entire modPow operation mu = bigPower / modulus; } // Reduces x modulo modulus, assuming x < modulus^2 - Base256 reduce(const Base256 &x) const { + BigInt reduce(const BigInt &x) const { if (x < modulus) { return x; } // q1 = x >> (k-1) size_t shift1 = (k > 1) ? (k - 1) : 0; - Base256 q1 = shiftRightBytes(x, shift1); + BigInt q1 = shiftRightLimbs(x, shift1); // q2 = q1 * mu - Base256 q2 = q1 * mu; + BigInt q2 = q1 * mu; // q3 = q2 >> (k+1) - Base256 q3 = shiftRightBytes(q2, k + 1); + BigInt q3 = shiftRightLimbs(q2, k + 1); // r = x - q3 * modulus - Base256 r = x - (q3 * modulus); + BigInt r = x - (q3 * modulus); // Since q3 is an approximation, we adjust at most twice while (r >= modulus) { @@ -62,15 +62,15 @@ namespace operations::math { }; } - bool isOdd(const Base256 &val) { + bool isOdd(const BigInt &val) { const auto &bytes = val.getBytes(); if (bytes.empty()) return false; return (bytes[0] & 1) != 0; } - Base256 divideByTwo(const Base256 &val) { + BigInt divideByTwo(const BigInt &val) { auto bytes = val.getBytes(); - if (bytes.empty()) return Base256(0); + if (bytes.empty()) return BigInt(0); std::uint64_t carry = 0; @@ -80,40 +80,40 @@ namespace operations::math { carry = next_carry; } - Base256::normalizeVector(bytes); + BigInt::normalizeVector(bytes); - return Base256(bytes); + return BigInt(bytes); } - Base256 gcd(const Base256 &a, const Base256 &b) { - Base256 tempA = a; - Base256 tempB = b; - while (tempB != Base256(0)) { - const Base256 t = tempB; + BigInt gcd(const BigInt &a, const BigInt &b) { + BigInt tempA = a; + BigInt tempB = b; + while (tempB != BigInt(0)) { + const BigInt t = tempB; tempB = tempA % tempB; tempA = t; } return tempA; } - Base256 modInverse(const Base256 &a, const Base256 &m) { - Base256 r0 = m; - Base256 r1 = a; - Base256 x0(0); - Base256 x1(1); + BigInt modInverse(const BigInt &a, const BigInt &m) { + BigInt r0 = m; + BigInt r1 = a; + BigInt x0(0); + BigInt x1(1); bool sign0 = true; bool sign1 = true; - while (r1 != Base256(0)) { - Base256 q = r0 / r1; - Base256 r2 = r0 % r1; + while (r1 != BigInt(0)) { + BigInt q = r0 / r1; + BigInt r2 = r0 % r1; - Base256 x2(0); + BigInt x2(0); bool sign2 = true; if (sign0 == sign1) { - Base256 qx = q * x1; + BigInt qx = q * x1; if (x0 >= qx) { x2 = x0 - qx; sign2 = sign0; @@ -134,8 +134,8 @@ namespace operations::math { sign1 = sign2; } - if (r0 != Base256(1)) { - return Base256(0); + if (r0 != BigInt(1)) { + return BigInt(0); } if (!sign0) { @@ -144,10 +144,10 @@ namespace operations::math { return x0 % m; } - bool isPrime(const Base256 &n) { - if (n <= Base256(1)) return false; - if (n == Base256(2) || n == Base256(3)) return true; - if (n % Base256(2) == Base256(0)) return false; + bool isPrime(const BigInt &n) { + if (n <= BigInt(1)) return false; + if (n == BigInt(2) || n == BigInt(3)) return true; + if (n % BigInt(2) == BigInt(0)) return false; static const std::vector smallPrimes = { 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, @@ -158,12 +158,12 @@ namespace operations::math { 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499 }; for (const std::uint32_t p: smallPrimes) { - Base256 primeObj(p); + BigInt primeObj(p); if (n == primeObj) return true; - if (n % primeObj == Base256(0)) return false; + if (n % primeObj == BigInt(0)) return false; } - Base256 d = n - Base256(1); + BigInt d = n - BigInt(1); std::uint32_t s = 0; while (!isZero(d.getBytes()) && !isOdd(d)) { d = divideByTwo(d); @@ -173,19 +173,19 @@ namespace operations::math { static const std::vector bases = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}; for (uint32_t baseVal: bases) { - Base256 a(baseVal); + BigInt a(baseVal); if (a >= n) break; - Base256 x = modPow(a, d, n); + BigInt x = modPow(a, d, n); - if (x == Base256(1) || x == n - Base256(1)) { + if (x == BigInt(1) || x == n - BigInt(1)) { continue; } bool composite = true; for (uint32_t r = 1; r < s; r++) { - x = modPow(x, Base256(2), n); - if (x == n - Base256(1)) { + x = modPow(x, BigInt(2), n); + if (x == n - BigInt(1)) { composite = false; break; } @@ -199,9 +199,9 @@ namespace operations::math { return true; } - Base256 pow(const Base256 &a, const std::uint64_t &power) { - Base256 result(1); - Base256 base = a; + BigInt pow(const BigInt &a, const std::uint64_t &power) { + BigInt result(1); + BigInt base = a; std::uint64_t p = power; while (p > 0) { if (p % 2 == 1) { @@ -213,10 +213,10 @@ namespace operations::math { return result; } - Base256 modPow(Base256 base, Base256 exponent, const Base256 &modulus) { - if (modulus <= Base256(1)) return Base256(0); + BigInt modPow(BigInt base, BigInt exponent, const BigInt &modulus) { + if (modulus <= BigInt(1)) return BigInt(0); - Base256 result(1); + BigInt result(1); // Initialize Barrett Reducer. // This does exactly one slow division to precompute mu. @@ -236,4 +236,4 @@ namespace operations::math { } return result; } -} // namespace operations::math \ No newline at end of file +} // namespace operations::math diff --git a/src/math_utils.h b/src/math_utils.h index a2a43fe..4a0814d 100644 --- a/src/math_utils.h +++ b/src/math_utils.h @@ -1,24 +1,24 @@ #ifndef MATH_UTILS_H #define MATH_UTILS_H -#include "base256.h" +#include "bigint.h" namespace operations::math { - bool isOdd(const Base256 &val); - Base256 divideByTwo(const Base256 &val); + bool isOdd(const BigInt &val); + BigInt divideByTwo(const BigInt &val); // Computes the greatest common divisor of a and b - Base256 gcd(const Base256 &a, const Base256 &b); + BigInt gcd(const BigInt &a, const BigInt &b); // Computes the modular multiplicative inverse of a modulo m - Base256 modInverse(const Base256 &a, const Base256 &m); + BigInt modInverse(const BigInt &a, const BigInt &m); // Performs the Miller-Rabin primality test on n - bool isPrime(const Base256 &n); + bool isPrime(const BigInt &n); - Base256 pow(const Base256 &a, const std::uint64_t &pow); + BigInt pow(const BigInt &a, const std::uint64_t &pow); - Base256 modPow(Base256 base, Base256 exponent, const Base256 &modulus); + BigInt modPow(BigInt base, BigInt exponent, const BigInt &modulus); } // namespace operations::math diff --git a/src/types.h b/src/types.h index 7a13120..bf23cc4 100644 --- a/src/types.h +++ b/src/types.h @@ -1,9 +1,9 @@ -#ifndef RSA_ENCRYPTOR_TYPES_H -#define RSA_ENCRYPTOR_TYPES_H +#ifndef BIGINT_TYPES_H +#define BIGINT_TYPES_H #include using ByteArray = std::vector; constexpr int INVALID_START_BIT_INDEX = -1; -#endif // RSA_ENCRYPTOR_TYPES_H +#endif // BIGINT_TYPES_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9b1cef8..1130524 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,17 +14,17 @@ FetchContent_MakeAvailable(Catch2) list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) include(Catch) -add_executable(Base256-Tests - test_base256.cpp +add_executable(BigInt-Tests + test_bigint.cpp ) -target_link_libraries(Base256-Tests +target_link_libraries(BigInt-Tests PRIVATE - Base256 + BigInt Catch2::Catch2WithMain ) # Catch2 integration in CTest -catch_discover_tests(Base256-Tests - TEST_PREFIX base256: +catch_discover_tests(BigInt-Tests + TEST_PREFIX bigint: WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) diff --git a/tests/test_base256.cpp b/tests/test_bigint.cpp similarity index 82% rename from tests/test_base256.cpp rename to tests/test_bigint.cpp index 5693a03..3b3ae66 100644 --- a/tests/test_base256.cpp +++ b/tests/test_bigint.cpp @@ -4,65 +4,65 @@ #include #include -#include "../src/base256.h" +#include "../src/bigint.h" #include "../src/math_utils.h" -using operations::Base256; +using operations::BigInt; using namespace operations::math; -static Base256 make(const uint64_t v) { return Base256(v); } +static BigInt make(const uint64_t v) { return BigInt(v); } // Helper for maximum uint64_t value constexpr uint64_t MAX_U64 = std::numeric_limits::max(); -TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { +TEST_CASE("BigInt: constructors, copy, assignment, self-assignment") { SECTION("Default is zero") { - Base256 a; + BigInt a; REQUIRE(a == make(0)); } SECTION("Construct from uint64_t") { - Base256 a(42); + BigInt a(42); REQUIRE(a == make(42)); REQUIRE(a != make(41)); } SECTION("Construct from max uint64_t") { - Base256 a(MAX_U64); + BigInt a(MAX_U64); REQUIRE(a == make(MAX_U64)); REQUIRE(a > make(MAX_U64 - 1)); } SECTION("Copy constructor copies value") { - Base256 a(123456789); - Base256 b(a); + BigInt a(123456789); + BigInt b(a); REQUIRE(b == a); } SECTION("Assignment copies value") { - Base256 a(777); - Base256 b(888); + BigInt a(777); + BigInt b(888); b = a; REQUIRE(b == a); } SECTION("Self-assignment is safe") { - Base256 a(424242); - Base256 &ref = a; + BigInt a(424242); + BigInt &ref = a; a = ref; // self-assign REQUIRE(a == make(424242)); } } -TEST_CASE("Base256: equality and inequality") { - Base256 a(0), b(0), c(1), d(MAX_U64); +TEST_CASE("BigInt: equality and inequality") { + BigInt a(0), b(0), c(1), d(MAX_U64); REQUIRE(a == b); REQUIRE(a != c); REQUIRE(d == make(MAX_U64)); REQUIRE(d != make(0)); } -TEST_CASE("Base256: addition & compound addition") { +TEST_CASE("BigInt: addition & compound addition") { SECTION("Simple add") { REQUIRE(make(10) + make(20) == make(30)); } SECTION("Add zero is identity") { @@ -79,9 +79,9 @@ TEST_CASE("Base256: addition & compound addition") { } SECTION("Addition exceeding uint64_t (8 bytes to 9 bytes)") { - Base256 a(MAX_U64); // 0xFFFFFFFFFFFFFFFF - Base256 b(2); - Base256 c = a + b; + BigInt a(MAX_U64); // 0xFFFFFFFFFFFFFFFF + BigInt b(2); + BigInt c = a + b; // We can't verify with make() because it exceeds uint64_t. // But we know (MAX_U64 + 2) - 2 should be MAX_U64 @@ -90,7 +90,7 @@ TEST_CASE("Base256: addition & compound addition") { } SECTION("Compound += operator") { - Base256 a(100); + BigInt a(100); a += make(50); REQUIRE(a == make(150)); a += make(0); @@ -106,7 +106,7 @@ TEST_CASE("Base256: addition & compound addition") { } } -TEST_CASE("Base256: subtraction & compound subtraction") { +TEST_CASE("BigInt: subtraction & compound subtraction") { SECTION("Simple sub") { REQUIRE(make(30) - make(20) == make(10)); } SECTION("Subtract zero is identity") { REQUIRE(make(123456) - make(0) == make(123456)); } @@ -132,7 +132,7 @@ TEST_CASE("Base256: subtraction & compound subtraction") { } SECTION("Compound -= operator") { - Base256 a(100); + BigInt a(100); a -= make(40); REQUIRE(a == make(60)); a -= make(60); @@ -140,7 +140,7 @@ TEST_CASE("Base256: subtraction & compound subtraction") { } } -TEST_CASE("Base256: multiplication & compound multiplication") { +TEST_CASE("BigInt: multiplication & compound multiplication") { SECTION("Simple mul") { SECTION("Simple mul") { REQUIRE(make(7) * make(6) == make(42)); } @@ -159,9 +159,9 @@ TEST_CASE("Base256: multiplication & compound multiplication") { } SECTION("Large multiplication (Exceeding uint64_t)") { - Base256 a(4294967295); // 0xFFFFFFFF (4 bytes) - Base256 b(4294967295); - Base256 c = a * b; // Should be 0xFFFFFFFE00000001 (8 bytes) + BigInt a(4294967295); // 0xFFFFFFFF (4 bytes) + BigInt b(4294967295); + BigInt c = a * b; // Should be 0xFFFFFFFE00000001 (8 bytes) // Verify via division REQUIRE(c / a == b); @@ -169,7 +169,7 @@ TEST_CASE("Base256: multiplication & compound multiplication") { } SECTION("Compound *= operator") { - Base256 a(10); + BigInt a(10); a *= make(10); REQUIRE(a == make(100)); a *= make(0); @@ -185,7 +185,7 @@ TEST_CASE("Base256: multiplication & compound multiplication") { } } -TEST_CASE("Base256: division & compound division") { +TEST_CASE("BigInt: division & compound division") { SECTION("Exact division") { REQUIRE(make(42) / make(6) == make(7)); REQUIRE(make(65536) / make(256) == make(256)); @@ -210,7 +210,7 @@ TEST_CASE("Base256: division & compound division") { } SECTION("Compound /= operator") { - Base256 a(100); + BigInt a(100); a /= make(2); REQUIRE(a == make(50)); a /= make(50); @@ -218,7 +218,7 @@ TEST_CASE("Base256: division & compound division") { } } -TEST_CASE("Base256: division by zero (edge cases)") { +TEST_CASE("BigInt: division by zero (edge cases)") { // Tests the left side of your OR statement: `isZero(divisor)` SECTION("Dividing a normal number by zero yields zero") { REQUIRE(make(42) / make(0) == make(0)); @@ -228,7 +228,7 @@ TEST_CASE("Base256: division by zero (edge cases)") { SECTION("Dividing zero by zero yields zero") { REQUIRE(make(0) / make(0) == make(0)); } } -TEST_CASE("Base256: remainder / modulo logic") { +TEST_CASE("BigInt: remainder / modulo logic") { // Assuming you have implemented operator% (e.g., a % b) // If not, adapt this to test your exposed remainder API or `div()` method @@ -257,27 +257,27 @@ TEST_CASE("Base256: remainder / modulo logic") { } } -TEST_CASE("Base256: mixed expressions & combinations") { +TEST_CASE("BigInt: mixed expressions & combinations") { SECTION("(a + b) * c then divide back by c (c > 0)") { for (uint64_t a = 1; a <= 20; ++a) for (uint64_t b = 1; b <= 20; ++b) for (uint64_t c = 1; c <= 20; ++c) { - Base256 additionResult = make(a) + make(b); - Base256 expr = additionResult * make(c); - Base256 divisionResult = expr / make(c); + BigInt additionResult = make(a) + make(b); + BigInt expr = additionResult * make(c); + BigInt divisionResult = expr / make(c); REQUIRE(divisionResult == additionResult); } } SECTION("Chained arithmetic: ((100 * 256) + 50 - 25) / 5 == 5125") { - Base256 result = ((make(100) * make(256)) + make(50) - make(25)) / make(5); + BigInt result = ((make(100) * make(256)) + make(50) - make(25)) / make(5); REQUIRE(result == make(5125)); } } -TEST_CASE("Base256: comparisons edge cases") { - Base256 a0(0), a1(1), a2(2), a255(255), a256(256); - Base256 max64(MAX_U64); +TEST_CASE("BigInt: comparisons edge cases") { + BigInt a0(0), a1(1), a2(2), a255(255), a256(256); + BigInt max64(MAX_U64); SECTION("Basic ordering") { REQUIRE(a0 < a1); @@ -290,8 +290,8 @@ TEST_CASE("Base256: comparisons edge cases") { } SECTION("Equality vs ordering on same values") { - Base256 x(123456); - Base256 y(123456); + BigInt x(123456); + BigInt y(123456); REQUIRE(x == y); REQUIRE_FALSE(x != y); @@ -319,15 +319,15 @@ TEST_CASE("Base256: comparisons edge cases") { REQUIRE(max64 > a256); REQUIRE(a0 < max64); - Base256 overflow = max64 + a1; // Exceeds uint64_t + BigInt overflow = max64 + a1; // Exceeds uint64_t REQUIRE(overflow > max64); REQUIRE(max64 < overflow); } } -TEST_CASE("Base256: print outputs base10 string to cout") { +TEST_CASE("BigInt: print outputs base10 string to cout") { // Helper lambda to capture std::cout output safely - auto capturePrint = [](const Base256 &num) { + auto capturePrint = [](const BigInt &num) { std::stringstream buffer; // Redirect std::cout to our buffer std::streambuf *oldCout = std::cout.rdbuf(buffer.rdbuf()); @@ -359,27 +359,27 @@ TEST_CASE("Base256: print outputs base10 string to cout") { SECTION("Extreme cases: Exceeding uint64_t limitations") { // MAX_U64 + 1 - Base256 overflowPlusOne = make(MAX_U64) + make(1); + BigInt overflowPlusOne = make(MAX_U64) + make(1); REQUIRE(capturePrint(overflowPlusOne) == "18446744073709551616\n"); // MAX_U64 * 10 - Base256 overflowTimesTen = make(MAX_U64) * make(10); + BigInt overflowTimesTen = make(MAX_U64) * make(10); REQUIRE(capturePrint(overflowTimesTen) == "184467440737095516150\n"); } } -TEST_CASE("Base256: power edge cases") { +TEST_CASE("BigInt: power edge cases") { // Test 255^2 (Should be 65025, or [0x01, 0xFE] in little-endian) - Base256 res = pow(Base256(255), 2); - REQUIRE(res == Base256(65025)); + BigInt res = pow(BigInt(255), 2); + REQUIRE(res == BigInt(65025)); - // Test a power that results in a huge number of trailing zeros in Base-256 - // 256^2 should be [0, 0, 1] - Base256 largePower = pow(Base256(2), 16); - REQUIRE(largePower == Base256(65536)); + // Test a power that crosses multiple byte boundaries. + // 256^2 should be 65536. + BigInt largePower = pow(BigInt(2), 16); + REQUIRE(largePower == BigInt(65536)); } -TEST_CASE("Base256: modPow (modular exponentiation)") { +TEST_CASE("BigInt: modPow (modular exponentiation)") { SECTION("Known small results") { REQUIRE(modPow(make(2), make(10), make(1000)) == make(24)); REQUIRE(modPow(make(7), make(3), make(13)) == make(5)); @@ -401,7 +401,7 @@ TEST_CASE("Base256: modPow (modular exponentiation)") { } } -TEST_CASE("Base256: pow (exponentiation)") { +TEST_CASE("BigInt: pow (exponentiation)") { SECTION("Power of zero (x^0 = 1)") { REQUIRE(pow(make(10), 0) == make(1)); REQUIRE(pow(make(255), 0) == make(1)); @@ -437,13 +437,13 @@ TEST_CASE("Base256: pow (exponentiation)") { SECTION("Extreme case: 2^64 perfectly overflows 64-bit boundaries") { // 2^64 evaluates to 18,446,744,073,709,551,616 // Which is exactly MAX_U64 + 1 - Base256 twoTo64 = pow(make(2), 64); + BigInt twoTo64 = pow(make(2), 64); REQUIRE(twoTo64 == make(MAX_U64) + make(1)); } SECTION("Extreme case: 10^20 using print verification") { // 10^19 fits in uint64_t. 10^20 does not. - Base256 tenTo20 = pow(make(10), 20); + BigInt tenTo20 = pow(make(10), 20); std::stringstream buffer; std::streambuf *oldCout = std::cout.rdbuf(buffer.rdbuf()); @@ -454,16 +454,15 @@ TEST_CASE("Base256: pow (exponentiation)") { } SECTION("Extreme case: Large base, small exponent (256^4)") { - // 256^4 results in a very specific memory layout in Little-Endian Base-256 - // It requires exactly 5 bytes: [0, 0, 0, 0, 1] - Base256 res = pow(make(256), 4); + // 256^4 crosses several byte boundaries while still fitting in one limb. + BigInt res = pow(make(256), 4); // We can verify this computationally by dividing by 256 four times REQUIRE(res / make(256) / make(256) / make(256) / make(256) == make(1)); } } -TEST_CASE("Base256 Math Utils: gcd (Greatest Common Divisor)") { +TEST_CASE("BigInt Math Utils: gcd (Greatest Common Divisor)") { SECTION("GCD of zero and a number is the number itself") { REQUIRE(gcd(make(42), make(0)) == make(42)); REQUIRE(gcd(make(0), make(42)) == make(42)); @@ -486,7 +485,7 @@ TEST_CASE("Base256 Math Utils: gcd (Greatest Common Divisor)") { } } -TEST_CASE("Base256 Math Utils: modInverse (Modular Multiplicative Inverse)") { +TEST_CASE("BigInt Math Utils: modInverse (Modular Multiplicative Inverse)") { SECTION("Small modular inverse test cases") { // Verified: (3 * 4) % 11 = 12 % 11 = 1 REQUIRE(modInverse(make(3), make(11)) == make(4)); @@ -504,9 +503,9 @@ TEST_CASE("Base256 Math Utils: modInverse (Modular Multiplicative Inverse)") { // phi = (p - 1) * (q - 1) = 1000034000064 // e = 65537 // d = modInverse(e, phi) = 983264276609 - Base256 phi(1000034000064ULL); - Base256 e(65537); - Base256 d = modInverse(e, phi); + BigInt phi(1000034000064ULL); + BigInt e(65537); + BigInt d = modInverse(e, phi); REQUIRE(d == make(983264276609ULL)); @@ -515,7 +514,7 @@ TEST_CASE("Base256 Math Utils: modInverse (Modular Multiplicative Inverse)") { } } -TEST_CASE("Base256 Math Utils: isPrime (Primality Testing)") { +TEST_CASE("BigInt Math Utils: isPrime (Primality Testing)") { SECTION("Numbers less than or equal to 1 are not prime") { REQUIRE_FALSE(isPrime(make(0))); REQUIRE_FALSE(isPrime(make(1))); @@ -547,14 +546,14 @@ TEST_CASE("Base256 Math Utils: isPrime (Primality Testing)") { } } -TEST_CASE("Base256: Performance Benchmarks", "[.][benchmark]") { +TEST_CASE("BigInt: Performance Benchmarks", "[.][benchmark]") { // 2048-Bit-Numbers (256 Bytes) ByteArray bytesA(256, 0xAA); ByteArray bytesB(256, 0x55); - Base256 largeNum(bytesA); - Base256 divisor(3); - Base256 largeDivisor(bytesB); + BigInt largeNum(bytesA); + BigInt divisor(3); + BigInt largeDivisor(bytesB); BENCHMARK("Division: 2048-Bit / 3") { return largeNum / divisor; @@ -565,20 +564,20 @@ TEST_CASE("Base256: Performance Benchmarks", "[.][benchmark]") { }; BENCHMARK("ModPow: 2048-Bit ^ 65537 mod 2048-Bit (RSA)") { - Base256 exponent(65537); + BigInt exponent(65537); return modPow(largeNum, exponent, largeDivisor); }; } -TEST_CASE("Base256: modPow Performance Benchmarks", "[.][benchmark][modpow]") { +TEST_CASE("BigInt: modPow Performance Benchmarks", "[.][benchmark][modpow]") { ByteArray base_bytes(256, 0xAA); ByteArray exp_bytes(256, 0x55); ByteArray mod_bytes(256, 0xFF); - Base256 base(base_bytes); - Base256 exponent_large(exp_bytes); - Base256 exponent_small(65537); - Base256 modulus(mod_bytes); + BigInt base(base_bytes); + BigInt exponent_large(exp_bytes); + BigInt exponent_small(65537); + BigInt modulus(mod_bytes); BENCHMARK("modPow: 2048-Bit ^ 65537 mod 2048-Bit (Verschluesselung)") { return modPow(base, exponent_small, modulus); @@ -587,4 +586,4 @@ TEST_CASE("Base256: modPow Performance Benchmarks", "[.][benchmark][modpow]") { BENCHMARK("modPow: 2048-Bit ^ 2048-Bit mod 2048-Bit (Entschluesselung)") { return modPow(base, exponent_large, modulus); }; -} \ No newline at end of file +}