From 2f5f4edaaf2990ff8de37205ba611e682fb298b1 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Mon, 6 Oct 2025 14:51:14 +0200 Subject: [PATCH 01/42] start --- src/main.cpp | 13 ++ src/vec/operations.h | 519 +++++++++++++++++++++++-------------------- 2 files changed, 285 insertions(+), 247 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index dd0d359..afae604 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,19 @@ int main(int argc, char* argv[]) { bool publicKey = false; } arguments; + using operations::Base256; + Base256 a1(1); + + Base256 data1(400); + Base256 data2(400); + Base256 result(800); + + data2 += result; + + std::cout << a1.isEqual(data2, result) << std::endl; + + + // Check if there are additional arguments if (argc > 1) { // Check for additional arguments diff --git a/src/vec/operations.h b/src/vec/operations.h index 60308fd..5f53be0 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -2,332 +2,357 @@ #define VEC_OPERATIONS_H #include -#include #include namespace operations { -[[nodiscard]] std::uint64_t getStartBitIndex(const std::vector &a) { - std::uint64_t index = 0; - std::uint64_t bitsSince1 = 0; - - for (std::uint8_t number : a) { - // Skip 8 bits if all zero - if (number == 0) { - bitsSince1 += 8; - continue; - } - // Go over every bit in number - for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { - // Select the current bit using a bitmask - if ((number & 0b1 << i) != 0) { - // Increment the index by the bits since the last increment - index += bitsSince1 + 1; - bitsSince1 = 0; - } else { - bitsSince1++; +class Base256 { + public: + [[nodiscard]] std::uint64_t getStartBitIndex(const std::vector &a) { + std::uint64_t index = 0; + std::uint64_t bitsSince1 = 0; + + for (const std::uint8_t number : a) { + // Skip 8 bits if all zero + if (number == 0) { + bitsSince1 += 8; + continue; + } + // Go over every bit in number + for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { + // Select the current bit using a bitmask + if ((number & 0b1 << i) != 0) { + // Increment the index by the bits since the last increment + index += bitsSince1 + 1; + bitsSince1 = 0; + } else { + bitsSince1++; + } } } + + // Returns position of the first bit needed for the number + return index - 1; } - // Returns position of the first bit needed for the number - return index - 1; -} + // This function picks one bit from pickNumber and places it at the least significant position + // of number. + [[nodiscard]] std::vector addBitFromNumber( + const std::vector &number, const std::vector &pickNumber, + const std::uint32_t index) { + std::vector result; -// This function picks one bit from pickNumber and places it at the least significant position of -// number. -[[nodiscard]] std::vector addBitFromNumber( - const std::vector &number, const std::vector &pickNumber, - std::uint32_t index) { - std::vector result; + // Check if the index is valid + if (index > getStartBitIndex(pickNumber)) { + return {0}; + } - // Check if the index is valid - if (index > getStartBitIndex(pickNumber)) { - return {0}; - } + // Check if the bit at the given index is set + bool mostSignificantBit = pickNumber[index / 8] & 0b1 << index % 8; + + for (std::uint8_t currentByte : number) { + result.push_back((currentByte << 1) + mostSignificantBit); - // Check if the bit at the given index is set - bool mostSignificantBit = pickNumber[index / 8] & 0b1 << index % 8; + // Check if the most significant bit of the current byte is set + mostSignificantBit = currentByte & 0b1000000 == 0b10000000; + } - for (std::uint8_t currentByte : number) { - result.push_back((currentByte << 1) + mostSignificantBit); + // In case the length of the vector and the actual number length matches, the + // number increases by one vector element with value 1 + if (mostSignificantBit) result.push_back(0b1); - // Check if the most significant bit of the current byte is set - mostSignificantBit = currentByte & 0b1000000 == 0b10000000; + return result; } - // In case the length of the vector and the actual number length matches, the numberincreases by - // one vector element with value 1 - if (mostSignificantBit) result.push_back(0b1); - - return result; -} + [[nodiscard]] bool isZero(const std::vector &a) { + for (std::uint8_t number : a) { + if (number != 0) return false; + } -[[nodiscard]] bool isZero(const std::vector &a) { - for (std::uint8_t number : a) { - if (number != 0) return false; + return true; } - return true; -} - -[[nodiscard]] bool isEqual(const std::vector &a, - const std::vector &b) noexcept { - const std::uint32_t aSize = a.size(); - const std::uint32_t bSize = b.size(); - const std::uint64_t iterations = aSize > bSize ? aSize : bSize; + [[nodiscard]] bool isEqual(const std::vector &a, + const std::vector &b) noexcept { + const std::uint32_t aSize = a.size(); + const std::uint32_t bSize = b.size(); + const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - // In case both numbers are the same length these variables could point ot the same vector - const std::uint32_t shortest = aSize < bSize ? aSize : bSize; - const std::vector longest = aSize > bSize ? a : b; + // In case both numbers are the same length these variables could point ot the same vector + const std::uint32_t shortest = aSize < bSize ? aSize : bSize; + const std::vector longest = aSize > bSize ? a : b; - for (std::uint64_t i = 0; i < iterations; i++) { - if (i >= shortest) { - if (longest[i] != 0) return false; - } else { - if (a[i] != b[i]) return false; + for (std::uint64_t i = 0; i < iterations; i++) { + if (i >= shortest) { + if (longest[i] != 0) return false; + } else { + if (a[i] != b[i]) return false; + } } + + return true; } - return true; -} + [[nodiscard]] bool isEqual(Base256 &a, Base256 &b) { + return isEqual(a.data, b.data); + } -[[nodiscard]] bool isBigger(const std::vector &a, - const std::vector &b) noexcept { - const std::uint32_t aSize = a.size(); - const std::uint32_t bSize = b.size(); - const std::uint64_t iterations = aSize > bSize ? aSize : bSize; + [[nodiscard]] bool isBigger(const std::vector &a, + const std::vector &b) noexcept { + const std::uint32_t aSize = a.size(); + const std::uint32_t bSize = b.size(); + const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - // We loop reverse throw all the vectors to catch leading zeros - for (std::int64_t i = iterations - 1; i >= 0; i--) { - const std::uint8_t aValue = (i < aSize) ? a[i] : 0; - const std::uint8_t bValue = (i < bSize) ? b[i] : 0; + // We loop reverse throw all the vectors to catch leading zeros + for (std::int64_t i = iterations - 1; i >= 0; i--) { + const std::uint8_t aValue = (i < aSize) ? a[i] : 0; + const std::uint8_t bValue = (i < bSize) ? b[i] : 0; - if (aValue > bValue) { - return true; + if (aValue > bValue) { + return true; + } } + + return false; } - return false; -} + explicit Base256(std::uint64_t initalValue) { + data = convertToVector(initalValue); + } -[[nodiscard]] std::vector add(const std::vector &a, - const std::vector &b) noexcept { - // Time complexity O(iterations) - // Initialize the result vector to store the sum - std::vector result; + void add(const std::vector &b) noexcept { + // Time complexity O(iterations) + // Initialize the result vector to store the sum + std::vector result; - // Get the max iterations based on the largest vector - const int iterations = std::max(a.size(), b.size()); + // Get the max iterations based on the largest vector + const int iterations = std::max(data.size(), b.size()); - // Carry to handle overflow between bytes - std::uint16_t carry = 0; + // Carry to handle overflow between bytes + std::uint16_t carry = 0; - for (int i = 0; i < iterations; i++) { - // Set up a holder for the sum - std::uint16_t sum = carry; + for (int i = 0; i < iterations; i++) { + // Set up a holder for the sum + std::uint16_t sum = carry; - // Only add to sum if we actually have a value in a - if (i < a.size()) { - sum += a[i]; - } + // Only add to sum if we actually have a value in a + if (i < data.size()) { + sum += data[i]; + } - // Only add to sum if we actually have a value in b - if (i < b.size()) { - sum += b[i]; + // Only add to sum if we actually have a value in b + if (i < b.size()) { + sum += b[i]; + } + + // Calculate the carry which is the overflow beyond 255 + carry = sum >> 8; + + // Clear out everything except the lsb + sum &= 0xFF; + + // Append the least significant byte of the sum to the result + result.push_back(static_cast(sum)); } - // Calculate the carry which is the overflow beyond 255 - carry = sum >> 8; + // If we got a carry we append this as well + if (carry) { + result.push_back(static_cast(carry)); + } - // Clear out everything except the lsb - sum &= 0xFF; + data = std::move(result); + } - // Append the least significant byte of the sum to the result - result.push_back(static_cast(sum)); + void add(const Base256& rhs) { + add(rhs.data); } - // If we got a carry we append this as well - if (carry) { - result.push_back(static_cast(carry)); + + Base256& operator+=(const Base256& rhs) { + add(rhs); // mutates *this->vec_ + return *this; // return LHS by reference for chaining } - return result; -} -// The return value can only be positive, if it would be negative, 0 is returned -[[nodiscard]] std::vector sub(const std::vector &a, - const std::vector &b) noexcept { - // Prevent from ending the subtraction before going over the hole subtractor and stop if the - // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(a)) return {0}; + // The return value can only be positive, if it would be negative, 0 is returned + [[nodiscard]] std::vector sub(const std::vector &a, + const std::vector &b) noexcept { + // Prevent from ending the subtraction before going over the hole subtractor and stop if the + // result can only be negative + if (getStartBitIndex(b) > getStartBitIndex(a)) return {0}; - std::vector result; + std::vector result; - // Handle an underflow when subtracting - bool borrow = false; + // Handle an underflow when subtracting + bool borrow = false; - for (int i = 0; i < a.size(); i++) { - std::int32_t subtract; - // Check for the end of the subtractor - if (i >= b.size()) { - subtract = borrow; - } else { - subtract = borrow + b[i]; - } - borrow = false; - - if (a[i] >= subtract) { - // If the current number is as least as big as subtract - std::uint8_t number = a[i] - subtract; - result.push_back(number); - } else { - // Borrow from the next number - subtract -= 256; - borrow = true; - - // Here subtract can only be 0 or negative - std::uint8_t number = a[i] - subtract; - result.push_back(number); + for (int i = 0; i < a.size(); i++) { + std::int32_t subtract; + // Check for the end of the subtractor + if (i >= b.size()) { + subtract = borrow; + } else { + subtract = borrow + b[i]; + } + borrow = false; + + if (a[i] >= subtract) { + // If the current number is as least as big as subtract + std::uint8_t number = a[i] - subtract; + result.push_back(number); + } else { + // Borrow from the next number + subtract -= 256; + borrow = true; + + // Here subtract can only be 0 or negative + std::uint8_t number = a[i] - subtract; + result.push_back(number); + } } + + return result; } - return result; -} + [[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept { + std::vector result; -[[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept { - std::vector result; + // Loop until the whole number is zero + while (number) { + // We only care for the lsb + result.push_back(static_cast(number & 0xFF)); - // Loop until the whole number is zero - while (number) { - // We only care for the lsb - result.push_back(static_cast(number & 0xFF)); + // Shift the number by 8 to the right + number >>= 8; + } - // Shift the number by 8 to the right - number >>= 8; + return result; } - return result; -} + [[nodiscard]] std::vector mul(const std::vector &a, + const std::vector &b) noexcept { + // Time complexity O(aSize * bSize) + const std::uint64_t aSize = a.size(); + const std::uint64_t bSize = b.size(); -[[nodiscard]] std::vector mul(const std::vector &a, - const std::vector &b) noexcept { - // Time complexity O(aSize * bSize) - const std::uint64_t aSize = a.size(); - const std::uint64_t bSize = b.size(); + // Initialize the result vector with zeros, with the size of aSize + bSize + std::vector result(aSize + bSize, 0); - // Initialize the result vector with zeros, with the size of aSize + bSize - std::vector result(aSize + bSize, 0); + for (std::uint64_t i = 0; i < aSize; i++) { + // Set up the carry value for each iteration + std::uint16_t carry = 0; - for (std::uint64_t i = 0; i < aSize; i++) { - // Set up the carry value for each iteration - std::uint16_t carry = 0; + for (uint64_t x = 0; x < bSize; x++) { + // Calculate the product by adding up the previous result, the carry, and the new + // product + std::uint16_t product = result[i + x] + carry + (a[i] * b[x]); - for (uint64_t x = 0; x < bSize; x++) { - // Calculate the product by adding up the previous result, the carry, and the new - // product - std::uint16_t product = result[i + x] + carry + (a[i] * b[x]); + // Calculate the carry which is the overflow beyond 255 + carry = product >> 8; - // Calculate the carry which is the overflow beyond 255 - carry = product >> 8; + // Store the least significant byte (lsb) of the product in the result + result[i + x] = static_cast(product & 0xFF); + } - // Store the least significant byte (lsb) of the product in the result - result[i + x] = static_cast(product & 0xFF); + // If there is a carry, append it to the result + // But with the offset of bSize because of the previous inner loop + result[i + bSize] += static_cast(carry); } - // If there is a carry, append it to the result - // But with the offset of bSize because of the previous inner loop - result[i + bSize] += static_cast(carry); + return result; } - return result; -} - -[[nodiscard]] std::vector div( - const std::vector dividend, const std::vector &divisor, - std::vector *remaining = nullptr) noexcept { - std::vector quotient; - std::uint8_t quotientBuffer = 0; - std::uint16_t quotientBitIndex = 0; - - // The index of the last bit in the dividendMask inside dividend - std::int64_t dividendIndex = getStartBitIndex(dividend); - // This copy's the most significant bit of the dividend - std::vector dividendMask = addBitFromNumber({0}, dividend, dividendIndex--); - - while (dividendIndex >= -1) { - if (quotientBitIndex > 7) { - // The quotient is stored from the most significant byte to the least significant - // byte, in contrast to all the other vector based numbers. - // Which is later reversed, at the end of the function. - quotient.push_back(quotientBuffer); - quotientBuffer = 0; - quotientBitIndex = 0; - } + [[nodiscard]] std::vector div( + const std::vector dividend, const std::vector &divisor, + std::vector *remaining = nullptr) noexcept { + std::vector quotient; + std::uint8_t quotientBuffer = 0; + std::uint16_t quotientBitIndex = 0; + + // The index of the last bit in the dividendMask inside dividend + std::int64_t dividendIndex = getStartBitIndex(dividend); + // This copy's the most significant bit of the dividend + std::vector dividendMask = addBitFromNumber({0}, dividend, dividendIndex--); + + while (dividendIndex >= -1) { + if (quotientBitIndex > 7) { + // The quotient is stored from the most significant byte to the least significant + // byte, in contrast to all the other vector based numbers. + // Which is later reversed, at the end of the function. + quotient.push_back(quotientBuffer); + quotientBuffer = 0; + quotientBitIndex = 0; + } - if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { - // Stop the loop if the dividend is smaller than the divisor, because fractional digits - // are not supported - if (dividendIndex < 0) { - quotientBuffer <<= 1; + if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) { + quotientBuffer <<= 1; - // If remaining pointer is passed, set the remaining value - if (remaining != nullptr) *remaining = dividendMask; - break; - } + // If remaining pointer is passed, set the remaining value + if (remaining != nullptr) *remaining = dividendMask; + break; + } - // Shift the dividend and set the new bit as high - quotientBuffer <<= 1; - quotientBuffer++; - quotientBitIndex++; - - dividendMask = sub(dividendMask, divisor); - dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); - } else { - // Stop the loop if the dividend is smaller than the divisor, because fractional digits - // are not supported - if (dividendIndex < 0) { + // Shift the dividend and set the new bit as high quotientBuffer <<= 1; + quotientBuffer++; + quotientBitIndex++; - // If remaining pointer is passed, set the remaining value - if (remaining != nullptr) *remaining = dividendMask; - break; - } - - // if the divisor is bigger than the dividend, we need to shift the dividend and set the - // new bit as low - quotientBuffer <<= 1; - quotientBitIndex++; + dividendMask = sub(dividendMask, divisor); + dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); + } else { + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) { + quotientBuffer <<= 1; + + // If remaining pointer is passed, set the remaining value + if (remaining != nullptr) *remaining = dividendMask; + break; + } + + // if the divisor is bigger than the dividend, we need to shift the dividend and set + // the new bit as low + quotientBuffer <<= 1; + quotientBitIndex++; - // Stop the loop if the dividend is smaller than the divisor, because fractional digits - // are not supported - if (dividendIndex < 0) break; + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) break; - // Because dividendMask is smaller than divisor, we add the next bit from the dividend - dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); + // Because dividendMask is smaller than divisor, we add the next bit from the + // dividend + dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); + } } - } - if (quotientBuffer != 0) quotient.push_back(quotientBuffer); + if (quotientBuffer != 0) quotient.push_back(quotientBuffer); + + // Reverse the quotient + std::reverse(quotient.begin(), quotient.end()); - // Reverse the quotient - std::reverse(quotient.begin(), quotient.end()); + return quotient; + } - return quotient; -} + [[nodiscard]] std::vector pow(const std::vector &a, + const std::uint64_t &pow) noexcept { + // Copy the value from a into result while keeping a constant + std::vector result; + std::copy(a.begin(), a.end(), std::back_inserter(result)); -[[nodiscard]] std::vector pow(const std::vector &a, - const std::uint64_t &pow) noexcept { - // Copy the value from a into result while keeping a constant - std::vector result; - std::copy(a.begin(), a.end(), std::back_inserter(result)); + // Start the loop at 1, because the first number is already assigned to result + for (std::uint32_t i = 1; i < pow; i++) { + result = mul(result, a); + } - // Start the loop at 1, because the first number is already assigned to result - for (std::uint32_t i = 1; i < pow; i++) { - result = mul(result, a); + return result; } - return result; -} +private: + std::vector data; +}; + } // namespace operations #endif From a772624144760f341fa297af7cb48839c92f6e8b Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 7 Oct 2025 08:22:46 +0200 Subject: [PATCH 02/42] more --- src/main.cpp | 8 +++---- src/vec/operations.h | 52 +++++++++++++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index afae604..df0f628 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,16 +18,14 @@ int main(int argc, char* argv[]) { using operations::Base256; Base256 a1(1); - Base256 data1(400); + Base256 data1(200); Base256 data2(400); - Base256 result(800); + Base256 result(200); - data2 += result; + data2 -= data1; std::cout << a1.isEqual(data2, result) << std::endl; - - // Check if there are additional arguments if (argc > 1) { // Check for additional arguments diff --git a/src/vec/operations.h b/src/vec/operations.h index 5f53be0..bacd662 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -166,26 +166,32 @@ class Base256 { add(rhs.data); } - Base256& operator+=(const Base256& rhs) { - add(rhs); // mutates *this->vec_ - return *this; // return LHS by reference for chaining + add(rhs); + return *this; + } + + void sub(const Base256& rhs) { + sub(rhs.data); } + Base256& operator-=(const Base256& rhs) { + sub(rhs); + return *this; + } // The return value can only be positive, if it would be negative, 0 is returned - [[nodiscard]] std::vector sub(const std::vector &a, - const std::vector &b) noexcept { + void sub(const std::vector &b) noexcept { // Prevent from ending the subtraction before going over the hole subtractor and stop if the // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(a)) return {0}; + if (getStartBitIndex(b) > getStartBitIndex(data)) throw std::invalid_argument("Invalid base256 index"); std::vector result; // Handle an underflow when subtracting bool borrow = false; - for (int i = 0; i < a.size(); i++) { + for (int i = 0; i < data.size(); i++) { std::int32_t subtract; // Check for the end of the subtractor if (i >= b.size()) { @@ -195,9 +201,9 @@ class Base256 { } borrow = false; - if (a[i] >= subtract) { + if (data[i] >= subtract) { // If the current number is as least as big as subtract - std::uint8_t number = a[i] - subtract; + std::uint8_t number = data[i] - subtract; result.push_back(number); } else { // Borrow from the next number @@ -205,12 +211,12 @@ class Base256 { borrow = true; // Here subtract can only be 0 or negative - std::uint8_t number = a[i] - subtract; + std::uint8_t number = data[i] - subtract; result.push_back(number); } } - return result; + data = std::move(result); } [[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept { @@ -228,10 +234,18 @@ class Base256 { return result; } - [[nodiscard]] std::vector mul(const std::vector &a, - const std::vector &b) noexcept { + void mul(const Base256& rhs) { + mul(rhs.data); + } + + Base256& operator*=(const Base256& rhs) { + mul(rhs); + return *this; + } + + void mul(const std::vector &b) noexcept { // Time complexity O(aSize * bSize) - const std::uint64_t aSize = a.size(); + const std::uint64_t aSize = data.size(); const std::uint64_t bSize = b.size(); // Initialize the result vector with zeros, with the size of aSize + bSize @@ -244,7 +258,7 @@ class Base256 { for (uint64_t x = 0; x < bSize; x++) { // Calculate the product by adding up the previous result, the carry, and the new // product - std::uint16_t product = result[i + x] + carry + (a[i] * b[x]); + std::uint16_t product = result[i + x] + carry + (data[i] * b[x]); // Calculate the carry which is the overflow beyond 255 carry = product >> 8; @@ -258,7 +272,7 @@ class Base256 { result[i + bSize] += static_cast(carry); } - return result; + data = std::move(result); } [[nodiscard]] std::vector div( @@ -299,7 +313,8 @@ class Base256 { quotientBuffer++; quotientBitIndex++; - dividendMask = sub(dividendMask, divisor); + // TODO + //dividendMask = sub(dividendMask, divisor); dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); } else { // Stop the loop if the dividend is smaller than the divisor, because fractional @@ -343,7 +358,8 @@ class Base256 { // Start the loop at 1, because the first number is already assigned to result for (std::uint32_t i = 1; i < pow; i++) { - result = mul(result, a); + // TODO + //result = mul(result, a); } return result; From 67b4c1eab01d1a53473242af1d0b300f5d77ffb1 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 9 Oct 2025 11:42:34 +0200 Subject: [PATCH 03/42] more refactor --- src/CMakeLists.txt | 1 + src/main.cpp | 4 +- src/vec/operations.cpp | 177 ++++++++++++++++++++++++++++++++++++ src/vec/operations.h | 202 +++++------------------------------------ 4 files changed, 202 insertions(+), 182 deletions(-) create mode 100644 src/vec/operations.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a6aff9b..35a7de8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,4 +4,5 @@ add_executable(RSA-Encryptor key.cpp encryption.cpp cli.cpp + vec/operations.cpp ) diff --git a/src/main.cpp b/src/main.cpp index df0f628..92087fc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,9 +18,9 @@ int main(int argc, char* argv[]) { using operations::Base256; Base256 a1(1); - Base256 data1(200); + Base256 data1(100); Base256 data2(400); - Base256 result(200); + Base256 result(300); data2 -= data1; diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp new file mode 100644 index 0000000..dd9a53d --- /dev/null +++ b/src/vec/operations.cpp @@ -0,0 +1,177 @@ +#include "operations.h" + +#include + +[[nodiscard]] std::vector operations::Base256::sub( + const std::vector &a, const std::vector &b) noexcept { + // Prevent from ending the subtraction before going over the hole subtractor and stop if the + // result can only be negative + if (getStartBitIndex(b) > getStartBitIndex(a)) return {0}; + + std::vector result; + + // Handle an underflow when subtracting + bool borrow = false; + + for (int i = 0; i < a.size(); i++) { + std::int32_t subtract; + // Check for the end of the subtractor + if (i >= b.size()) { + subtract = borrow; + } else { + subtract = borrow + b[i]; + } + borrow = false; + + if (a[i] >= subtract) { + // If the current number is as least as big as subtract + std::uint8_t number = a[i] - subtract; + result.push_back(number); + } else { + // Borrow from the next number + subtract -= 256; + borrow = true; + + // Here subtract can only be 0 or negative + std::uint8_t number = a[i] - subtract; + result.push_back(number); + } + } + + return result; +} + +void operations::Base256::mul(const std::vector &b) noexcept { + // Time complexity O(aSize * bSize) + const std::uint64_t aSize = data.size(); + const std::uint64_t bSize = b.size(); + + // Initialize the result vector with zeros, with the size of aSize + bSize + std::vector result(aSize + bSize, 0); + + for (std::uint64_t i = 0; i < aSize; i++) { + // Set up the carry value for each iteration + std::uint16_t carry = 0; + + for (uint64_t x = 0; x < bSize; x++) { + // Calculate the product by adding up the previous result, the carry, and the new + // product + std::uint16_t product = result[i + x] + carry + (data[i] * b[x]); + + // Calculate the carry which is the overflow beyond 255 + carry = product >> 8; + + // Store the least significant byte (lsb) of the product in the result + result[i + x] = static_cast(product & 0xFF); + } + + // If there is a carry, append it to the result + // But with the offset of bSize because of the previous inner loop + result[i + bSize] += static_cast(carry); + } + + data = std::move(result); +} + +void operations::Base256::div(const std::vector &divisor, + std::vector *remaining) noexcept { + std::vector quotient; + std::uint8_t quotientBuffer = 0; + std::uint16_t quotientBitIndex = 0; + + // The index of the last bit in the dividendMask inside dividend + std::int64_t dividendIndex = getStartBitIndex(data); + // This copy's the most significant bit of the dividend + std::vector dividendMask = addBitFromNumber({0}, data, dividendIndex--); + + while (dividendIndex >= -1) { + if (quotientBitIndex > 7) { + // The quotient is stored from the most significant byte to the least significant + // byte, in contrast to all the other vector based numbers. + // Which is later reversed, at the end of the function. + quotient.push_back(quotientBuffer); + quotientBuffer = 0; + quotientBitIndex = 0; + } + + if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) { + quotientBuffer <<= 1; + + // If remaining pointer is passed, set the remaining value + if (remaining != nullptr) *remaining = dividendMask; + break; + } + + // Shift the dividend and set the new bit as high + quotientBuffer <<= 1; + quotientBuffer++; + quotientBitIndex++; + + dividendMask = sub(dividendMask, divisor); + dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); + } else { + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) { + quotientBuffer <<= 1; + + // If remaining pointer is passed, set the remaining value + if (remaining != nullptr) *remaining = dividendMask; + break; + } + + // if the divisor is bigger than the dividend, we need to shift the dividend and set + // the new bit as low + quotientBuffer <<= 1; + quotientBitIndex++; + + // Stop the loop if the dividend is smaller than the divisor, because fractional + // digits are not supported + if (dividendIndex < 0) break; + + // Because dividendMask is smaller than divisor, we add the next bit from the + // dividend + dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); + } + } + + if (quotientBuffer != 0) quotient.push_back(quotientBuffer); + + // Reverse the quotient + std::reverse(quotient.begin(), quotient.end()); + + data = std::move(quotient); +} + +[[nodiscard]] std::vector operations::Base256::convertToVector(std::uint64_t number) noexcept { + std::vector result; + + // Loop until the whole number is zero + while (number) { + // We only care for the lsb + result.push_back(static_cast(number & 0xFF)); + + // Shift the number by 8 to the right + number >>= 8; + } + + return result; +} + +[[nodiscard]] std::vector operations::Base256::pow( + const std::vector &a, const std::uint64_t &pow) noexcept { + // Copy the value from a into result while keeping a constant + std::vector result; + std::copy(a.begin(), a.end(), std::back_inserter(result)); + + // Start the loop at 1, because the first number is already assigned to result + for (std::uint32_t i = 1; i < pow; i++) { + // TODO + // result = mul(result, a); + } + + return result; +} \ No newline at end of file diff --git a/src/vec/operations.h b/src/vec/operations.h index bacd662..5bd2790 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -2,6 +2,7 @@ #define VEC_OPERATIONS_H #include +#include #include namespace operations { @@ -162,207 +163,48 @@ class Base256 { data = std::move(result); } - void add(const Base256& rhs) { - add(rhs.data); - } - - Base256& operator+=(const Base256& rhs) { - add(rhs); - return *this; - } - - void sub(const Base256& rhs) { - sub(rhs.data); - } - - Base256& operator-=(const Base256& rhs) { - sub(rhs); - return *this; - } - // The return value can only be positive, if it would be negative, 0 is returned void sub(const std::vector &b) noexcept { // Prevent from ending the subtraction before going over the hole subtractor and stop if the // result can only be negative if (getStartBitIndex(b) > getStartBitIndex(data)) throw std::invalid_argument("Invalid base256 index"); - std::vector result; - - // Handle an underflow when subtracting - bool borrow = false; - - for (int i = 0; i < data.size(); i++) { - std::int32_t subtract; - // Check for the end of the subtractor - if (i >= b.size()) { - subtract = borrow; - } else { - subtract = borrow + b[i]; - } - borrow = false; - - if (data[i] >= subtract) { - // If the current number is as least as big as subtract - std::uint8_t number = data[i] - subtract; - result.push_back(number); - } else { - // Borrow from the next number - subtract -= 256; - borrow = true; - - // Here subtract can only be 0 or negative - std::uint8_t number = data[i] - subtract; - result.push_back(number); - } - } + std::vector result = sub(data, b); data = std::move(result); } - [[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept { - std::vector result; + [[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept; - // Loop until the whole number is zero - while (number) { - // We only care for the lsb - result.push_back(static_cast(number & 0xFF)); + // The return value can only be positive, if it would be negative, 0 is returned + [[nodiscard]] std::vector sub(const std::vector &a, + const std::vector &b) noexcept; - // Shift the number by 8 to the right - number >>= 8; - } + void mul(const std::vector &b) noexcept; + void div(const std::vector &divisor, + std::vector *remaining = nullptr) noexcept; - return result; - } - - void mul(const Base256& rhs) { - mul(rhs.data); - } + [[nodiscard]] std::vector pow(const std::vector &a, + const std::uint64_t &pow) noexcept; - Base256& operator*=(const Base256& rhs) { - mul(rhs); + Base256& operator+=(const Base256& rhs) { + add(rhs.data); return *this; } - void mul(const std::vector &b) noexcept { - // Time complexity O(aSize * bSize) - const std::uint64_t aSize = data.size(); - const std::uint64_t bSize = b.size(); - - // Initialize the result vector with zeros, with the size of aSize + bSize - std::vector result(aSize + bSize, 0); - - for (std::uint64_t i = 0; i < aSize; i++) { - // Set up the carry value for each iteration - std::uint16_t carry = 0; - - for (uint64_t x = 0; x < bSize; x++) { - // Calculate the product by adding up the previous result, the carry, and the new - // product - std::uint16_t product = result[i + x] + carry + (data[i] * b[x]); - - // Calculate the carry which is the overflow beyond 255 - carry = product >> 8; - - // Store the least significant byte (lsb) of the product in the result - result[i + x] = static_cast(product & 0xFF); - } - - // If there is a carry, append it to the result - // But with the offset of bSize because of the previous inner loop - result[i + bSize] += static_cast(carry); - } - - data = std::move(result); + Base256& operator-=(const Base256& rhs) { + sub(rhs.data); + return *this; } - [[nodiscard]] std::vector div( - const std::vector dividend, const std::vector &divisor, - std::vector *remaining = nullptr) noexcept { - std::vector quotient; - std::uint8_t quotientBuffer = 0; - std::uint16_t quotientBitIndex = 0; - - // The index of the last bit in the dividendMask inside dividend - std::int64_t dividendIndex = getStartBitIndex(dividend); - // This copy's the most significant bit of the dividend - std::vector dividendMask = addBitFromNumber({0}, dividend, dividendIndex--); - - while (dividendIndex >= -1) { - if (quotientBitIndex > 7) { - // The quotient is stored from the most significant byte to the least significant - // byte, in contrast to all the other vector based numbers. - // Which is later reversed, at the end of the function. - quotient.push_back(quotientBuffer); - quotientBuffer = 0; - quotientBitIndex = 0; - } - - if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported - if (dividendIndex < 0) { - quotientBuffer <<= 1; - - // If remaining pointer is passed, set the remaining value - if (remaining != nullptr) *remaining = dividendMask; - break; - } - - // Shift the dividend and set the new bit as high - quotientBuffer <<= 1; - quotientBuffer++; - quotientBitIndex++; - - // TODO - //dividendMask = sub(dividendMask, divisor); - dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); - } else { - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported - if (dividendIndex < 0) { - quotientBuffer <<= 1; - - // If remaining pointer is passed, set the remaining value - if (remaining != nullptr) *remaining = dividendMask; - break; - } - - // if the divisor is bigger than the dividend, we need to shift the dividend and set - // the new bit as low - quotientBuffer <<= 1; - quotientBitIndex++; - - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported - if (dividendIndex < 0) break; - - // Because dividendMask is smaller than divisor, we add the next bit from the - // dividend - dividendMask = addBitFromNumber(dividendMask, dividend, dividendIndex--); - } - } - - if (quotientBuffer != 0) quotient.push_back(quotientBuffer); - - // Reverse the quotient - std::reverse(quotient.begin(), quotient.end()); - - return quotient; + Base256& operator*=(const Base256& rhs) { + mul(rhs.data); + return *this; } - [[nodiscard]] std::vector pow(const std::vector &a, - const std::uint64_t &pow) noexcept { - // Copy the value from a into result while keeping a constant - std::vector result; - std::copy(a.begin(), a.end(), std::back_inserter(result)); - - // Start the loop at 1, because the first number is already assigned to result - for (std::uint32_t i = 1; i < pow; i++) { - // TODO - //result = mul(result, a); - } - - return result; + Base256& operator/=(const Base256& rhs) { + div(rhs.data); + return *this; } private: From a913a571961e80d67bd2e8f5c47f1bb050e62944 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 9 Oct 2025 12:33:09 +0200 Subject: [PATCH 04/42] stuff --- src/CMakeLists.txt | 1 + src/main.cpp | 2 +- src/vec/helper.h | 128 ++++++++++++++++++++++++++ src/vec/operations.cpp | 73 +++++++++++---- src/vec/operations.h | 197 +++++------------------------------------ 5 files changed, 210 insertions(+), 191 deletions(-) create mode 100644 src/vec/helper.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 35a7de8..5504a29 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,4 +5,5 @@ add_executable(RSA-Encryptor encryption.cpp cli.cpp vec/operations.cpp + vec/helper.h ) diff --git a/src/main.cpp b/src/main.cpp index 92087fc..224888f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,7 @@ int main(int argc, char* argv[]) { data2 -= data1; - std::cout << a1.isEqual(data2, result) << std::endl; + std::cout << (data2 == result) << std::endl; // Check if there are additional arguments if (argc > 1) { diff --git a/src/vec/helper.h b/src/vec/helper.h new file mode 100644 index 0000000..8ca1aae --- /dev/null +++ b/src/vec/helper.h @@ -0,0 +1,128 @@ +#ifndef RSA_ENCRYPTOR_HELPER_H +#define RSA_ENCRYPTOR_HELPER_H + +#include +#include + +[[nodiscard]] inline std::uint64_t getStartBitIndex(const std::vector &a) { + std::uint64_t index = 0; + std::uint64_t bitsSince1 = 0; + + for (const std::uint8_t number : a) { + // Skip 8 bits if all zero + if (number == 0) { + bitsSince1 += 8; + continue; + } + // Go over every bit in number + for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { + // Select the current bit using a bitmask + if ((number & 0b1 << i) != 0) { + // Increment the index by the bits since the last increment + index += bitsSince1 + 1; + bitsSince1 = 0; + } else { + bitsSince1++; + } + } + } + + // Returns position of the first bit needed for the number + return index - 1; +} + +[[nodiscard]] inline std::vector convertToVector( + std::uint64_t number) noexcept { + std::vector result; + + // Loop until the whole number is zero + while (number) { + // We only care for the lsb + result.push_back(static_cast(number & 0xFF)); + + // Shift the number by 8 to the right + number >>= 8; + } + + return result; +} + +// This function picks one bit from pickNumber and places it at the least significant position +// of number. +[[nodiscard]] inline std::vector addBitFromNumber( + const std::vector &number, const std::vector &pickNumber, + const std::uint32_t index) { + std::vector result; + + // Check if the index is valid + if (index > getStartBitIndex(pickNumber)) { + return {0}; + } + + // Check if the bit at the given index is set + bool mostSignificantBit = pickNumber[index / 8] & 0b1 << index % 8; + + for (std::uint8_t currentByte : number) { + result.push_back((currentByte << 1) + mostSignificantBit); + + // Check if the most significant bit of the current byte is set + // TODO investigate operation evaluation order problem + mostSignificantBit = currentByte & 0b1000000 == 0b10000000; + } + + // In case the length of the vector and the actual number length matches, the + // number increases by one vector element with value 1 + if (mostSignificantBit) result.push_back(0b1); + + return result; +} + +[[nodiscard]] inline bool isBigger(const std::vector &a, + const std::vector &b) noexcept { + const std::uint32_t aSize = a.size(); + const std::uint32_t bSize = b.size(); + const std::uint64_t iterations = aSize > bSize ? aSize : bSize; + + // We loop reverse throw all the vectors to catch leading zeros + for (std::int64_t i = iterations - 1; i >= 0; i--) { + const std::uint8_t aValue = (i < aSize) ? a[i] : 0; + const std::uint8_t bValue = (i < bSize) ? b[i] : 0; + + if (aValue > bValue) { + return true; + } + } + + return false; +} + +[[nodiscard]] inline bool isEqual(const std::vector &a, + const std::vector &b) noexcept { + const std::uint32_t aSize = a.size(); + const std::uint32_t bSize = b.size(); + const std::uint64_t iterations = aSize > bSize ? aSize : bSize; + + // In case both numbers are the same length these variables could point ot the same vector + const std::uint32_t shortest = aSize < bSize ? aSize : bSize; + const std::vector longest = aSize > bSize ? a : b; + + for (std::uint64_t i = 0; i < iterations; i++) { + if (i >= shortest) { + if (longest[i] != 0) return false; + } else { + if (a[i] != b[i]) return false; + } + } + + return true; +} + +[[nodiscard]] inline bool isZero(const std::vector &a) { + for (const std::uint8_t number : a) { + if (number != 0) return false; + } + + return true; +} + +#endif // RSA_ENCRYPTOR_HELPER_H diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index dd9a53d..1a56f33 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -2,6 +2,49 @@ #include +void operations::Base256::add(const std::vector &b) noexcept { + // Time complexity O(iterations) + // Initialize the result vector to store the sum + std::vector result; + + // Get the max iterations based on the largest vector + const int iterations = std::max(data.size(), b.size()); + + // Carry to handle overflow between bytes + std::uint16_t carry = 0; + + for (int i = 0; i < iterations; i++) { + // Set up a holder for the sum + std::uint16_t sum = carry; + + // Only add to sum if we actually have a value in a + if (i < data.size()) { + sum += data[i]; + } + + // Only add to sum if we actually have a value in b + if (i < b.size()) { + sum += b[i]; + } + + // Calculate the carry which is the overflow beyond 255 + carry = sum >> 8; + + // Clear out everything except the lsb + sum &= 0xFF; + + // Append the least significant byte of the sum to the result + result.push_back(static_cast(sum)); + } + + // If we got a carry we append this as well + if (carry) { + result.push_back(static_cast(carry)); + } + + data = std::move(result); +} + [[nodiscard]] std::vector operations::Base256::sub( const std::vector &a, const std::vector &b) noexcept { // Prevent from ending the subtraction before going over the hole subtractor and stop if the @@ -41,6 +84,19 @@ return result; } +// The return value can only be positive, if it would be negative, 0 is returned +void operations::Base256::sub(const std::vector &b) noexcept { + // Prevent from ending the subtraction before going over the hole subtractor and stop if the + // result can only be negative + if (getStartBitIndex(b) > getStartBitIndex(data)) { + std::cerr << "Invalid base256 index" << std::endl; + } + + std::vector result = sub(data, b); + + data = std::move(result); +} + void operations::Base256::mul(const std::vector &b) noexcept { // Time complexity O(aSize * bSize) const std::uint64_t aSize = data.size(); @@ -146,24 +202,9 @@ void operations::Base256::div(const std::vector &divisor, data = std::move(quotient); } -[[nodiscard]] std::vector operations::Base256::convertToVector(std::uint64_t number) noexcept { - std::vector result; - - // Loop until the whole number is zero - while (number) { - // We only care for the lsb - result.push_back(static_cast(number & 0xFF)); - - // Shift the number by 8 to the right - number >>= 8; - } - - return result; -} - [[nodiscard]] std::vector operations::Base256::pow( const std::vector &a, const std::uint64_t &pow) noexcept { - // Copy the value from a into result while keeping a constant + // Copy the value from an into result while keeping a constant std::vector result; std::copy(a.begin(), a.end(), std::back_inserter(result)); diff --git a/src/vec/operations.h b/src/vec/operations.h index 5bd2790..ba14f41 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -2,212 +2,61 @@ #define VEC_OPERATIONS_H #include +#include +#include #include #include +#include "helper.h" + namespace operations { class Base256 { public: - [[nodiscard]] std::uint64_t getStartBitIndex(const std::vector &a) { - std::uint64_t index = 0; - std::uint64_t bitsSince1 = 0; - - for (const std::uint8_t number : a) { - // Skip 8 bits if all zero - if (number == 0) { - bitsSince1 += 8; - continue; - } - // Go over every bit in number - for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { - // Select the current bit using a bitmask - if ((number & 0b1 << i) != 0) { - // Increment the index by the bits since the last increment - index += bitsSince1 + 1; - bitsSince1 = 0; - } else { - bitsSince1++; - } - } - } - - // Returns position of the first bit needed for the number - return index - 1; - } - - // This function picks one bit from pickNumber and places it at the least significant position - // of number. - [[nodiscard]] std::vector addBitFromNumber( - const std::vector &number, const std::vector &pickNumber, - const std::uint32_t index) { - std::vector result; - - // Check if the index is valid - if (index > getStartBitIndex(pickNumber)) { - return {0}; - } - - // Check if the bit at the given index is set - bool mostSignificantBit = pickNumber[index / 8] & 0b1 << index % 8; - - for (std::uint8_t currentByte : number) { - result.push_back((currentByte << 1) + mostSignificantBit); - - // Check if the most significant bit of the current byte is set - mostSignificantBit = currentByte & 0b1000000 == 0b10000000; - } - - // In case the length of the vector and the actual number length matches, the - // number increases by one vector element with value 1 - if (mostSignificantBit) result.push_back(0b1); - - return result; - } - - [[nodiscard]] bool isZero(const std::vector &a) { - for (std::uint8_t number : a) { - if (number != 0) return false; - } - - return true; - } - - [[nodiscard]] bool isEqual(const std::vector &a, - const std::vector &b) noexcept { - const std::uint32_t aSize = a.size(); - const std::uint32_t bSize = b.size(); - const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - - // In case both numbers are the same length these variables could point ot the same vector - const std::uint32_t shortest = aSize < bSize ? aSize : bSize; - const std::vector longest = aSize > bSize ? a : b; - - for (std::uint64_t i = 0; i < iterations; i++) { - if (i >= shortest) { - if (longest[i] != 0) return false; - } else { - if (a[i] != b[i]) return false; - } - } - - return true; - } - - [[nodiscard]] bool isEqual(Base256 &a, Base256 &b) { - return isEqual(a.data, b.data); - } - - [[nodiscard]] bool isBigger(const std::vector &a, - const std::vector &b) noexcept { - const std::uint32_t aSize = a.size(); - const std::uint32_t bSize = b.size(); - const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - - // We loop reverse throw all the vectors to catch leading zeros - for (std::int64_t i = iterations - 1; i >= 0; i--) { - const std::uint8_t aValue = (i < aSize) ? a[i] : 0; - const std::uint8_t bValue = (i < bSize) ? b[i] : 0; - - if (aValue > bValue) { - return true; - } - } - return false; - } - - explicit Base256(std::uint64_t initalValue) { + explicit Base256(const std::uint64_t initalValue) { data = convertToVector(initalValue); } - void add(const std::vector &b) noexcept { - // Time complexity O(iterations) - // Initialize the result vector to store the sum - std::vector result; - - // Get the max iterations based on the largest vector - const int iterations = std::max(data.size(), b.size()); - - // Carry to handle overflow between bytes - std::uint16_t carry = 0; - - for (int i = 0; i < iterations; i++) { - // Set up a holder for the sum - std::uint16_t sum = carry; - - // Only add to sum if we actually have a value in a - if (i < data.size()) { - sum += data[i]; - } - - // Only add to sum if we actually have a value in b - if (i < b.size()) { - sum += b[i]; - } - - // Calculate the carry which is the overflow beyond 255 - carry = sum >> 8; - - // Clear out everything except the lsb - sum &= 0xFF; - - // Append the least significant byte of the sum to the result - result.push_back(static_cast(sum)); - } - - // If we got a carry we append this as well - if (carry) { - result.push_back(static_cast(carry)); - } - - data = std::move(result); - } - - // The return value can only be positive, if it would be negative, 0 is returned - void sub(const std::vector &b) noexcept { - // Prevent from ending the subtraction before going over the hole subtractor and stop if the - // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(data)) throw std::invalid_argument("Invalid base256 index"); - - std::vector result = sub(data, b); - - data = std::move(result); - } - - [[nodiscard]] std::vector convertToVector(std::uint64_t number) noexcept; - - // The return value can only be positive, if it would be negative, 0 is returned - [[nodiscard]] std::vector sub(const std::vector &a, + void add(const std::vector &b) noexcept; + void sub(const std::vector &b) noexcept; + std::vector sub(const std::vector &a, const std::vector &b) noexcept; - void mul(const std::vector &b) noexcept; void div(const std::vector &divisor, - std::vector *remaining = nullptr) noexcept; + std::vector *remaining = nullptr) noexcept; - [[nodiscard]] std::vector pow(const std::vector &a, + std::vector pow(const std::vector &a, const std::uint64_t &pow) noexcept; - Base256& operator+=(const Base256& rhs) { + Base256 &operator+=(const Base256 &rhs) { add(rhs.data); return *this; } - Base256& operator-=(const Base256& rhs) { + Base256 &operator-=(const Base256 &rhs) { sub(rhs.data); return *this; } - Base256& operator*=(const Base256& rhs) { + Base256 &operator*=(const Base256 &rhs) { mul(rhs.data); return *this; } - Base256& operator/=(const Base256& rhs) { + Base256 &operator/=(const Base256 &rhs) { div(rhs.data); return *this; } -private: + [[nodiscard]] friend bool operator==(const Base256& lhs, const Base256& rhs) { + return isEqual(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator!=(const Base256& lhs, const Base256& rhs) { + return !isEqual(lhs.data, rhs.data); + } + + private: std::vector data; }; From c4a784b50b05206c1c5209997fa8ee089ad8585f Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 9 Oct 2025 15:36:00 +0200 Subject: [PATCH 05/42] implement more operations --- src/main.cpp | 7 +++---- src/vec/operations.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 224888f..a8643a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,15 +16,14 @@ int main(int argc, char* argv[]) { } arguments; using operations::Base256; - Base256 a1(1); Base256 data1(100); Base256 data2(400); - Base256 result(300); + Base256 result(0); - data2 -= data1; + result = data1 + data2; - std::cout << (data2 == result) << std::endl; + std::cout << (result == Base256(500)) << std::endl; // Check if there are additional arguments if (argc > 1) { diff --git a/src/vec/operations.h b/src/vec/operations.h index ba14f41..6d228fe 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -17,6 +17,10 @@ class Base256 { data = convertToVector(initalValue); } + Base256(const Base256& base256) { + data = base256.data; + } + void add(const std::vector &b) noexcept; void sub(const std::vector &b) noexcept; std::vector sub(const std::vector &a, @@ -28,21 +32,53 @@ class Base256 { std::vector pow(const std::vector &a, const std::uint64_t &pow) noexcept; + Base256& operator=(const Base256& other) { + // We protect us from self assignment + if (this != &other) { + data = other.data; + } + return *this; + } + + friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result += lhs; + return result; + } + Base256 &operator+=(const Base256 &rhs) { add(rhs.data); return *this; } + friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result -= lhs; + return result; + } + Base256 &operator-=(const Base256 &rhs) { sub(rhs.data); return *this; } + friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result *= lhs; + return result; + } + Base256 &operator*=(const Base256 &rhs) { mul(rhs.data); return *this; } + friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result /= lhs; + return result; + } + Base256 &operator/=(const Base256 &rhs) { div(rhs.data); return *this; From 10e114ddd5111cb29cb6d0313deb8bd3d26bb0ed Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Fri, 10 Oct 2025 09:28:59 +0200 Subject: [PATCH 06/42] stuff --- src/main.cpp | 4 ++-- src/vec/operations.cpp | 7 ------- src/vec/operations.h | 28 +++++++++++++++++++++++----- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a8643a7..bd35ac3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,11 +19,11 @@ int main(int argc, char* argv[]) { Base256 data1(100); Base256 data2(400); - Base256 result(0); + Base256 result = 200; result = data1 + data2; - std::cout << (result == Base256(500)) << std::endl; + std::cout << (result >= Base256(500)) << std::endl; // Check if there are additional arguments if (argc > 1) { diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index 1a56f33..2fbd4d1 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -86,14 +86,7 @@ void operations::Base256::add(const std::vector &b) noexcept { // The return value can only be positive, if it would be negative, 0 is returned void operations::Base256::sub(const std::vector &b) noexcept { - // Prevent from ending the subtraction before going over the hole subtractor and stop if the - // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(data)) { - std::cerr << "Invalid base256 index" << std::endl; - } - std::vector result = sub(data, b); - data = std::move(result); } diff --git a/src/vec/operations.h b/src/vec/operations.h index 6d228fe..edcca66 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -3,8 +3,6 @@ #include #include -#include -#include #include #include "helper.h" @@ -13,14 +11,18 @@ namespace operations { class Base256 { public: - explicit Base256(const std::uint64_t initalValue) { - data = convertToVector(initalValue); + Base256(const std::uint64_t initialValue) { + data = convertToVector(initialValue); } Base256(const Base256& base256) { data = base256.data; } + Base256() { + data = convertToVector(0); + } + void add(const std::vector &b) noexcept; void sub(const std::vector &b) noexcept; std::vector sub(const std::vector &a, @@ -35,7 +37,7 @@ class Base256 { Base256& operator=(const Base256& other) { // We protect us from self assignment if (this != &other) { - data = other.data; + data = std::move(other.data); } return *this; } @@ -92,6 +94,22 @@ class Base256 { return !isEqual(lhs.data, rhs.data); } + [[nodiscard]] friend bool operator>(const Base256& lhs, const Base256& rhs) { + return isBigger(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator<(const Base256& lhs, const Base256& rhs) { + return !isBigger(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator>=(const Base256& lhs, const Base256& rhs) { + return !isBigger(rhs.data, lhs.data); + } + + [[nodiscard]] friend bool operator<=(const Base256& lhs, const Base256& rhs) { + return !isBigger(lhs.data, rhs.data); + } + private: std::vector data; }; From 8d13a91a2f1b50add923060f3bd0db799bba987e Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Mon, 13 Oct 2025 13:54:48 +0200 Subject: [PATCH 07/42] hmm --- .gitignore | 2 + src/CMakeLists.txt | 54 ++++++- src/Testing/Temporary/CTestCostData.txt | 1 + src/Testing/Temporary/LastTest.log | 3 + src/main.cpp | 2 + src/tests/test_base256.cpp | 197 ++++++++++++++++++++++++ src/vec/operations.cpp | 5 +- src/vec/operations.h | 11 +- 8 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 src/Testing/Temporary/CTestCostData.txt create mode 100644 src/Testing/Temporary/LastTest.log create mode 100644 src/tests/test_base256.cpp diff --git a/.gitignore b/.gitignore index 9f1755b..b688f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .idea /build +/src/build +/Testing /cmake-build-debug .vscode diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5504a29..5bdc691 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,55 @@ -add_executable(RSA-Encryptor - main.cpp +cmake_minimum_required(VERSION 3.20) +project(RSA-Encryptor CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Enable testing early +include(CTest) # defines BUILD_TESTING (ON by default) and enable_testing() + +# --- Catch2 via FetchContent --- +include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.6.0 +) +FetchContent_MakeAvailable(Catch2) + +# Tell CMake where to find Catch2's CMake helpers (Catch.cmake) +# (Required when Catch2 is brought in via FetchContent and not installed system-wide) +list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) +include(Catch) # provides catch_discover_tests() + +# --- Your core library (shared between app and tests) --- +add_library(rsa_core utility.cpp key.cpp encryption.cpp - cli.cpp vec/operations.cpp - vec/helper.h ) +target_include_directories(rsa_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# --- Main app --- +add_executable(RSA-Encryptor main.cpp cli.cpp) +target_link_libraries(RSA-Encryptor PRIVATE rsa_core) + +# --- Tests --- +if(BUILD_TESTING) + add_executable(RSA-Encryptor-Tests + tests/test_base256.cpp + ) + target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(RSA-Encryptor-Tests + PRIVATE + rsa_core + Catch2::Catch2WithMain + ) + + # For single-config generators (Make/Ninja), this “just works”. + # For multi-config (VS/Xcode), use: ctest -C Debug + catch_discover_tests(RSA-Encryptor-Tests + TEST_PREFIX base256: + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endif() \ No newline at end of file diff --git a/src/Testing/Temporary/CTestCostData.txt b/src/Testing/Temporary/CTestCostData.txt new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/src/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/src/Testing/Temporary/LastTest.log b/src/Testing/Temporary/LastTest.log new file mode 100644 index 0000000..216e635 --- /dev/null +++ b/src/Testing/Temporary/LastTest.log @@ -0,0 +1,3 @@ +Start testing: Oct 13 13:34 CEST +---------------------------------------------------------- +End testing: Oct 13 13:34 CEST diff --git a/src/main.cpp b/src/main.cpp index bd35ac3..ab31a03 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,8 @@ int main(int argc, char* argv[]) { Base256 data1(100); Base256 data2(400); + + data2.print(); Base256 result = 200; result = data1 + data2; diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp new file mode 100644 index 0000000..f3c63e8 --- /dev/null +++ b/src/tests/test_base256.cpp @@ -0,0 +1,197 @@ +// Catch2 v3: use the macros-only header to keep compile times down +#include +#include + +// Adjust include path to match your project structure: +#include "vec/operations.h" + +using operations::Base256; + +static Base256 make(uint64_t v) { return Base256(v); } + +TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { + SECTION("Default is zero") { + Base256 a; + REQUIRE(a == make(0)); + } + + SECTION("Construct from uint64_t") { + Base256 a(42); + REQUIRE(a == make(42)); + REQUIRE(a != make(41)); + } + + SECTION("Copy constructor copies value") { + Base256 a(123456789); + Base256 b(a); + REQUIRE(b == a); + } + + SECTION("Assignment copies value") { + Base256 a(777); + Base256 b(888); + b = a; + REQUIRE(b == a); + } + + SECTION("Self-assignment is safe") { + Base256 a(424242); + Base256& ref = a; + a = ref; // self-assign + REQUIRE(a == make(424242)); + } +} + +TEST_CASE("Base256: equality and inequality") { + Base256 a(0), b(0), c(1); + REQUIRE(a == b); + REQUIRE(a != c); +} + +TEST_CASE("Base256: addition") { + SECTION("Simple add") { + REQUIRE(make(10) + make(20) == make(30)); + } + + SECTION("Carry across one byte") { + REQUIRE(make(255) + make(1) == make(256)); + } + + SECTION("Carry across multiple bytes") { + REQUIRE(make(65535) + make(1) == make(65536)); // 0xFFFF + 1 = 0x1'0000 + } + + SECTION("Add zero is identity") { + REQUIRE(make(123456) + make(0) == make(123456)); + REQUIRE(make(0) + make(123456) == make(123456)); + } + + SECTION("Commutativity: a + b == b + a (small domain)") { + for (uint64_t a = 0; a <= 200; ++a) { + for (uint64_t b = 0; b <= 200; ++b) { + REQUIRE(make(a) + make(b) == make(b) + make(a)); + } + } + } + + SECTION("Associativity: (a + b) + c == a + (b + c) (very small domain)") { + for (uint64_t a = 0; a <= 20; ++a) + for (uint64_t b = 0; b <= 20; ++b) + for (uint64_t c = 0; c <= 20; ++c) + REQUIRE((make(a) + make(b)) + make(c) == make(a) + (make(b) + make(c))); + } +} + +TEST_CASE("Base256: subtraction") { + SECTION("Simple sub") { + REQUIRE(make(30) - make(20) == make(10)); + } + + SECTION("Borrow across one byte") { + REQUIRE(make(256) - make(1) == make(255)); + } + + SECTION("Borrow across multiple bytes") { + REQUIRE(make(65536) - make(1) == make(65535)); + } + + SECTION("Subtract to zero") { + REQUIRE(make(123456) - make(123456) == make(0)); + } + + SECTION("Subtract zero is identity") { + REQUIRE(make(123456) - make(0) == make(123456)); + } + + // NOTE: If your implementation allows negative results, define behavior. + // Many big-int libs clamp or assume a >= b. Here we skip a 0)") { + for (uint64_t a = 1; a <= 50; ++a) + for (uint64_t b = 1; b <= 50; ++b) + for (uint64_t c = 1; c <= 50; ++c) { + Base256 expr = (make(a) + make(b)) * make(c); + REQUIRE(expr / make(c) == make(a) + make(b)); + } + } +} + +TEST_CASE("Base256: comparisons") { + Base256 a0(0), a1(1), a2(2), a255(255), a256(256); + + SECTION("Basic ordering") { + REQUIRE(a0 < a1); + REQUIRE(a1 < a2); + REQUIRE(a255 < a256); + + REQUIRE(a256 > a255); + REQUIRE(a2 > a1); + REQUIRE(a1 > a0); + } + + SECTION("Equality vs ordering") { + Base256 x(123456); + Base256 y(123456); + REQUIRE(x == y); + REQUIRE_FALSE(x != y); + + // Strictness: x < x must be false, x <= x must be true + REQUIRE_FALSE(x < x); // <-- this will FAIL with your current operator< + REQUIRE(x <= x); + REQUIRE_FALSE(x > x); + REQUIRE(x >= x); + } + + SECTION("Cross-byte comparisons") { + REQUIRE(make(256) > make(255)); + REQUIRE(make(65536) > make(65535)); + REQUIRE(make(65536) >= make(65536)); + REQUIRE_FALSE(make(65536) < make(65536)); + } +} diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index 2fbd4d1..b4f8096 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -49,7 +49,10 @@ void operations::Base256::add(const std::vector &b) noexcept { const std::vector &a, const std::vector &b) noexcept { // Prevent from ending the subtraction before going over the hole subtractor and stop if the // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(a)) return {0}; + if (getStartBitIndex(b) > getStartBitIndex(a)) { + std::cout << "bonjour" << std::endl; + return {0}; + } std::vector result; diff --git a/src/vec/operations.h b/src/vec/operations.h index edcca66..5613315 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -34,10 +34,19 @@ class Base256 { std::vector pow(const std::vector &a, const std::uint64_t &pow) noexcept; + void print() const { + std::uint64_t value = 0; + // Reconstruct assuming big-endian: data[0] is most significant byte + for (std::uint8_t byte : data) { + value = (value << 8) | static_cast(byte); + } + std::cout << value << '\n'; + } + Base256& operator=(const Base256& other) { // We protect us from self assignment if (this != &other) { - data = std::move(other.data); + data = other.data; } return *this; } From a8453143512c62e235938c5abc2664b0e374b0d8 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 14 Oct 2025 11:06:09 +0200 Subject: [PATCH 08/42] cleanup cmakelist --- src/CMakeLists.txt | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5bdc691..50cbe43 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,37 +4,35 @@ project(RSA-Encryptor CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Enable testing early -include(CTest) # defines BUILD_TESTING (ON by default) and enable_testing() +include(CTest) -# --- Catch2 via FetchContent --- +# Catch2 via FetchContent include(FetchContent) FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.6.0 + GIT_TAG v3.11.0 ) FetchContent_MakeAvailable(Catch2) -# Tell CMake where to find Catch2's CMake helpers (Catch.cmake) -# (Required when Catch2 is brought in via FetchContent and not installed system-wide) +# Find Catch 2 CMakeList list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) -include(Catch) # provides catch_discover_tests() +include(Catch) -# --- Your core library (shared between app and tests) --- -add_library(rsa_core +# Core library +add_library(RSA-Core utility.cpp key.cpp encryption.cpp vec/operations.cpp ) -target_include_directories(rsa_core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -# --- Main app --- +# RSA-Encryptor Main add_executable(RSA-Encryptor main.cpp cli.cpp) -target_link_libraries(RSA-Encryptor PRIVATE rsa_core) +target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) -# --- Tests --- +# Tests if(BUILD_TESTING) add_executable(RSA-Encryptor-Tests tests/test_base256.cpp @@ -42,12 +40,11 @@ if(BUILD_TESTING) target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(RSA-Encryptor-Tests PRIVATE - rsa_core + RSA-Core Catch2::Catch2WithMain ) - # For single-config generators (Make/Ninja), this “just works”. - # For multi-config (VS/Xcode), use: ctest -C Debug + # TODO inspect single config generators and multi config generators catch_discover_tests(RSA-Encryptor-Tests TEST_PREFIX base256: WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} From b2dbcbc49a12610a77064b524e62ca0c3d133fc2 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 14 Oct 2025 11:12:07 +0200 Subject: [PATCH 09/42] stuff --- src/tests/test_base256.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index f3c63e8..5ae1b36 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -1,8 +1,6 @@ -// Catch2 v3: use the macros-only header to keep compile times down #include #include -// Adjust include path to match your project structure: #include "vec/operations.h" using operations::Base256; From d7fa3054abc76c96e5fe150236de4420c56f6530 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Fri, 17 Oct 2025 08:58:30 +0200 Subject: [PATCH 10/42] add testing script --- src/test.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/test.sh diff --git a/src/test.sh b/src/test.sh new file mode 100644 index 0000000..e990865 --- /dev/null +++ b/src/test.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +BUILD_DIR="build" + +echo "Cleaning build dir to avoid stale FetchContent deps" +rm -rf "$BUILD_DIR" + +echo "Configuring (Debug, tests ON, Clang, Unix Makefiles)" +cmake -S . -B "$BUILD_DIR" -G "Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DBUILD_TESTING=ON \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_CXX_EXTENSIONS=OFF \ + -DFETCHCONTENT_UPDATES_DISCONNECTED=ON + +echo "Building the executable" +cmake --build "$BUILD_DIR" --target RSA-Encryptor-Tests -- -j"$(nproc)" + +echo "Running tests" +ctest --test-dir "$BUILD_DIR" --output-on-failure \ No newline at end of file From ae60d4b7024505804cbd2eccfa9039697e7deeec Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 28 Apr 2026 12:17:04 +0200 Subject: [PATCH 11/42] fix implementation using gemini 3.1 pro --- src/Testing/Temporary/CTestCostData.txt | 1 - src/Testing/Temporary/LastTest.log | 3 - src/test.sh | 8 ++- src/tests/test_base256.cpp | 9 +-- src/vec/helper.h | 50 +++++-------- src/vec/operations.cpp | 93 +++++++++++-------------- src/vec/operations.h | 8 +-- 7 files changed, 75 insertions(+), 97 deletions(-) delete mode 100644 src/Testing/Temporary/CTestCostData.txt delete mode 100644 src/Testing/Temporary/LastTest.log diff --git a/src/Testing/Temporary/CTestCostData.txt b/src/Testing/Temporary/CTestCostData.txt deleted file mode 100644 index ed97d53..0000000 --- a/src/Testing/Temporary/CTestCostData.txt +++ /dev/null @@ -1 +0,0 @@ ---- diff --git a/src/Testing/Temporary/LastTest.log b/src/Testing/Temporary/LastTest.log deleted file mode 100644 index 216e635..0000000 --- a/src/Testing/Temporary/LastTest.log +++ /dev/null @@ -1,3 +0,0 @@ -Start testing: Oct 13 13:34 CEST ----------------------------------------------------------- -End testing: Oct 13 13:34 CEST diff --git a/src/test.sh b/src/test.sh index e990865..78ed26d 100644 --- a/src/test.sh +++ b/src/test.sh @@ -2,9 +2,11 @@ set -euo pipefail BUILD_DIR="build" +FETCH_DIR="$BUILD_DIR/_deps" -echo "Cleaning build dir to avoid stale FetchContent deps" -rm -rf "$BUILD_DIR" +echo "Cleaning only FetchContent deps to avoid stale dependencies" +# Remove only the FetchContent directory, not the entire build +rm -rf "$FETCH_DIR" echo "Configuring (Debug, tests ON, Clang, Unix Makefiles)" cmake -S . -B "$BUILD_DIR" -G "Unix Makefiles" \ @@ -18,4 +20,4 @@ echo "Building the executable" cmake --build "$BUILD_DIR" --target RSA-Encryptor-Tests -- -j"$(nproc)" echo "Running tests" -ctest --test-dir "$BUILD_DIR" --output-on-failure \ No newline at end of file +ctest --test-dir "$BUILD_DIR" --output-on-failure diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index 5ae1b36..ee8469b 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -101,7 +101,6 @@ TEST_CASE("Base256: subtraction") { REQUIRE(make(123456) - make(0) == make(123456)); } - // NOTE: If your implementation allows negative results, define behavior. // Many big-int libs clamp or assume a >= b. Here we skip a x); REQUIRE(x >= x); diff --git a/src/vec/helper.h b/src/vec/helper.h index 8ca1aae..87d3a4c 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -4,21 +4,17 @@ #include #include -[[nodiscard]] inline std::uint64_t getStartBitIndex(const std::vector &a) { - std::uint64_t index = 0; - std::uint64_t bitsSince1 = 0; +[[nodiscard]] inline std::int64_t getStartBitIndex(const std::vector &a) { + std::int64_t index = 0; + std::int64_t bitsSince1 = 0; for (const std::uint8_t number : a) { - // Skip 8 bits if all zero if (number == 0) { bitsSince1 += 8; continue; } - // Go over every bit in number for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { - // Select the current bit using a bitmask - if ((number & 0b1 << i) != 0) { - // Increment the index by the bits since the last increment + if ((number & (0b1 << i)) != 0) { index += bitsSince1 + 1; bitsSince1 = 0; } else { @@ -27,7 +23,7 @@ } } - // Returns position of the first bit needed for the number + // Returning signed int64_t stops negative values from underflowing return index - 1; } @@ -35,43 +31,36 @@ std::uint64_t number) noexcept { std::vector result; - // Loop until the whole number is zero + // Securely ensure 0 results in at least [0], never[] + if (number == 0) return {0}; + while (number) { - // We only care for the lsb result.push_back(static_cast(number & 0xFF)); - - // Shift the number by 8 to the right number >>= 8; } return result; } -// This function picks one bit from pickNumber and places it at the least significant position -// of number. [[nodiscard]] inline std::vector addBitFromNumber( const std::vector &number, const std::vector &pickNumber, - const std::uint32_t index) { + const std::int64_t index) { std::vector result; - // Check if the index is valid - if (index > getStartBitIndex(pickNumber)) { + // Validated boundary access + if (index < 0 || index > getStartBitIndex(pickNumber)) { return {0}; } - // Check if the bit at the given index is set - bool mostSignificantBit = pickNumber[index / 8] & 0b1 << index % 8; + bool mostSignificantBit = (pickNumber[index / 8] & (0b1 << (index % 8))) != 0; for (std::uint8_t currentByte : number) { - result.push_back((currentByte << 1) + mostSignificantBit); - - // Check if the most significant bit of the current byte is set - // TODO investigate operation evaluation order problem - mostSignificantBit = currentByte & 0b1000000 == 0b10000000; + // FIX: The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets overwritten/shifted. + bool nextMSB = (currentByte & 0x80) != 0; + result.push_back(static_cast((currentByte << 1) | mostSignificantBit)); + mostSignificantBit = nextMSB; } - // In case the length of the vector and the actual number length matches, the - // number increases by one vector element with value 1 if (mostSignificantBit) result.push_back(0b1); return result; @@ -83,13 +72,14 @@ const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - // We loop reverse throw all the vectors to catch leading zeros for (std::int64_t i = iterations - 1; i >= 0; i--) { const std::uint8_t aValue = (i < aSize) ? a[i] : 0; const std::uint8_t bValue = (i < bSize) ? b[i] : 0; if (aValue > bValue) { return true; + } else if (aValue < bValue) { + return false; // FIX: Ensure false is explicitly returned if the checking bit drops behind. } } @@ -102,7 +92,6 @@ const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; - // In case both numbers are the same length these variables could point ot the same vector const std::uint32_t shortest = aSize < bSize ? aSize : bSize; const std::vector longest = aSize > bSize ? a : b; @@ -121,8 +110,7 @@ for (const std::uint8_t number : a) { if (number != 0) return false; } - return true; } -#endif // RSA_ENCRYPTOR_HELPER_H +#endif // RSA_ENCRYPTOR_HELPER_H \ No newline at end of file diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index b4f8096..fe2570c 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -42,17 +42,16 @@ void operations::Base256::add(const std::vector &b) noexcept { result.push_back(static_cast(carry)); } + // Strip mathematical leading zeros (trailing in little-endian representation) + while (result.size() > 1 && result.back() == 0) { + result.pop_back(); + } + data = std::move(result); } [[nodiscard]] std::vector operations::Base256::sub( const std::vector &a, const std::vector &b) noexcept { - // Prevent from ending the subtraction before going over the hole subtractor and stop if the - // result can only be negative - if (getStartBitIndex(b) > getStartBitIndex(a)) { - std::cout << "bonjour" << std::endl; - return {0}; - } std::vector result; @@ -84,6 +83,11 @@ void operations::Base256::add(const std::vector &b) noexcept { } } + // Strip trailing zeroes to normalize + while (result.size() > 1 && result.back() == 0) { + result.pop_back(); + } + return result; } @@ -122,78 +126,65 @@ void operations::Base256::mul(const std::vector &b) noexcept { result[i + bSize] += static_cast(carry); } + // Since aSize + bSize typically provides extra buffering, normalise the number safely + while (result.size() > 1 && result.back() == 0) { + result.pop_back(); + } + data = std::move(result); } void operations::Base256::div(const std::vector &divisor, std::vector *remaining) noexcept { - std::vector quotient; - std::uint8_t quotientBuffer = 0; - std::uint16_t quotientBitIndex = 0; + if (isZero(divisor)) { + data = {0}; + if (remaining != nullptr) *remaining = {0}; + return; + } + + std::int64_t initialDividendIndex = getStartBitIndex(data); + if (initialDividendIndex < 0) { + if (remaining != nullptr) *remaining = {0}; + data = {0}; + return; + } + + // Vector preallocated to support max potential bits mapped by initial index + std::vector quotient((initialDividendIndex / 8) + 1, 0); - // The index of the last bit in the dividendMask inside dividend - std::int64_t dividendIndex = getStartBitIndex(data); - // This copy's the most significant bit of the dividend + std::int64_t dividendIndex = initialDividendIndex; std::vector dividendMask = addBitFromNumber({0}, data, dividendIndex--); while (dividendIndex >= -1) { - if (quotientBitIndex > 7) { - // The quotient is stored from the most significant byte to the least significant - // byte, in contrast to all the other vector based numbers. - // Which is later reversed, at the end of the function. - quotient.push_back(quotientBuffer); - quotientBuffer = 0; - quotientBitIndex = 0; - } + // Evaluate mathematical power index representing current bit generated + std::int64_t currentQBitIndex = dividendIndex + 1; if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported - if (dividendIndex < 0) { - quotientBuffer <<= 1; + // Drop evaluating bit immediately at explicitly targeted position inside quotient + quotient[currentQBitIndex / 8] |= (1 << (currentQBitIndex % 8)); - // If remaining pointer is passed, set the remaining value - if (remaining != nullptr) *remaining = dividendMask; + if (dividendIndex < 0) { + if (remaining != nullptr) *remaining = sub(dividendMask, divisor); break; } - // Shift the dividend and set the new bit as high - quotientBuffer <<= 1; - quotientBuffer++; - quotientBitIndex++; - dividendMask = sub(dividendMask, divisor); dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); } else { - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported if (dividendIndex < 0) { - quotientBuffer <<= 1; - - // If remaining pointer is passed, set the remaining value if (remaining != nullptr) *remaining = dividendMask; break; } - // if the divisor is bigger than the dividend, we need to shift the dividend and set - // the new bit as low - quotientBuffer <<= 1; - quotientBitIndex++; - - // Stop the loop if the dividend is smaller than the divisor, because fractional - // digits are not supported - if (dividendIndex < 0) break; - - // Because dividendMask is smaller than divisor, we add the next bit from the - // dividend dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); } } - if (quotientBuffer != 0) quotient.push_back(quotientBuffer); - - // Reverse the quotient - std::reverse(quotient.begin(), quotient.end()); + // Strip trailing normalization zeros (empty space buffers) securely + while (quotient.size() > 1 && quotient.back() == 0) { + quotient.pop_back(); + } + if (quotient.empty()) quotient.push_back(0); data = std::move(quotient); } diff --git a/src/vec/operations.h b/src/vec/operations.h index 5613315..626af31 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -36,9 +36,9 @@ class Base256 { void print() const { std::uint64_t value = 0; - // Reconstruct assuming big-endian: data[0] is most significant byte - for (std::uint8_t byte : data) { - value = (value << 8) | static_cast(byte); + // Vector stores data natively as little-endian, iterate using reverse iterator + for (auto it = data.rbegin(); it != data.rend(); ++it) { + value = (value << 8) | static_cast(*it); } std::cout << value << '\n'; } @@ -108,7 +108,7 @@ class Base256 { } [[nodiscard]] friend bool operator<(const Base256& lhs, const Base256& rhs) { - return !isBigger(lhs.data, rhs.data); + return isBigger(rhs.data, lhs.data); } [[nodiscard]] friend bool operator>=(const Base256& lhs, const Base256& rhs) { From 4d803bdc42061df6005eb709c01d7e03c265debe Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 28 Apr 2026 12:34:29 +0200 Subject: [PATCH 12/42] fix cmakelist --- src/CMakeLists.txt | 7 ++++++- src/test.sh | 23 ----------------------- 2 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 src/test.sh diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50cbe43..22367c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,15 @@ cmake_minimum_required(VERSION 3.20) project(RSA-Encryptor CXX) +# Enforce C++20 and disable compiler-specific extensions set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +# Prevent CMake from re-downloading Catch2 every time CLion reloads +set(FETCHCONTENT_UPDATES_DISCONNECTED ON CACHE BOOL "Don't update FetchContent on every config" FORCE) + +# include(CTest) automatically sets BUILD_TESTING to ON by default include(CTest) # Catch2 via FetchContent @@ -44,7 +50,6 @@ if(BUILD_TESTING) Catch2::Catch2WithMain ) - # TODO inspect single config generators and multi config generators catch_discover_tests(RSA-Encryptor-Tests TEST_PREFIX base256: WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} diff --git a/src/test.sh b/src/test.sh deleted file mode 100644 index 78ed26d..0000000 --- a/src/test.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -BUILD_DIR="build" -FETCH_DIR="$BUILD_DIR/_deps" - -echo "Cleaning only FetchContent deps to avoid stale dependencies" -# Remove only the FetchContent directory, not the entire build -rm -rf "$FETCH_DIR" - -echo "Configuring (Debug, tests ON, Clang, Unix Makefiles)" -cmake -S . -B "$BUILD_DIR" -G "Unix Makefiles" \ - -DCMAKE_BUILD_TYPE=Debug \ - -DBUILD_TESTING=ON \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_CXX_EXTENSIONS=OFF \ - -DFETCHCONTENT_UPDATES_DISCONNECTED=ON - -echo "Building the executable" -cmake --build "$BUILD_DIR" --target RSA-Encryptor-Tests -- -j"$(nproc)" - -echo "Running tests" -ctest --test-dir "$BUILD_DIR" --output-on-failure From 400b4d4b06ae94836b33bca281caac4f0269bc5d Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 29 Apr 2026 13:52:59 +0200 Subject: [PATCH 13/42] expand unit tests --- src/tests/test_base256.cpp | 178 ++++++++++++++++++++++++++++--------- src/vec/operations.cpp | 5 ++ 2 files changed, 142 insertions(+), 41 deletions(-) diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index ee8469b..7cd254c 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include "vec/operations.h" @@ -7,6 +9,9 @@ using operations::Base256; static Base256 make(uint64_t v) { return Base256(v); } +// Helper for maximum uint64_t value +const uint64_t MAX_U64 = std::numeric_limits::max(); + TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { SECTION("Default is zero") { Base256 a; @@ -19,6 +24,12 @@ TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { REQUIRE(a != make(41)); } + SECTION("Construct from max uint64_t") { + Base256 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); @@ -41,27 +52,50 @@ TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { } TEST_CASE("Base256: equality and inequality") { - Base256 a(0), b(0), c(1); + Base256 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") { +TEST_CASE("Base256: addition & compound addition") { SECTION("Simple add") { REQUIRE(make(10) + make(20) == make(30)); } + SECTION("Add zero is identity") { + REQUIRE(make(123456) + make(0) == make(123456)); + REQUIRE(make(0) + make(123456) == make(123456)); + } + SECTION("Carry across one byte") { REQUIRE(make(255) + make(1) == make(256)); } - SECTION("Carry across multiple bytes") { - REQUIRE(make(65535) + make(1) == make(65536)); // 0xFFFF + 1 = 0x1'0000 + SECTION("Cascading carry across multiple bytes") { + REQUIRE(make(65535) + make(1) == make(65536)); // 0xFFFF + 1 = 0x10000 + REQUIRE(make(16777215) + make(1) == make(16777216)); // 0xFFFFFF + 1 + REQUIRE(make(4294967295) + make(1) == make(4294967296)); // 0xFFFFFFFF + 1 } - SECTION("Add zero is identity") { - REQUIRE(make(123456) + make(0) == make(123456)); - REQUIRE(make(0) + make(123456) == make(123456)); + SECTION("Addition exceeding uint64_t (8 bytes to 9 bytes)") { + Base256 a(MAX_U64); // 0xFFFFFFFFFFFFFFFF + Base256 b(2); + Base256 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 + REQUIRE(c - b == a); + REQUIRE(c > a); + } + + SECTION("Compound += operator") { + Base256 a(100); + a += make(50); + REQUIRE(a == make(150)); + a += make(0); + REQUIRE(a == make(150)); } SECTION("Commutativity: a + b == b + a (small domain)") { @@ -71,59 +105,81 @@ TEST_CASE("Base256: addition") { } } } - - SECTION("Associativity: (a + b) + c == a + (b + c) (very small domain)") { - for (uint64_t a = 0; a <= 20; ++a) - for (uint64_t b = 0; b <= 20; ++b) - for (uint64_t c = 0; c <= 20; ++c) - REQUIRE((make(a) + make(b)) + make(c) == make(a) + (make(b) + make(c))); - } } -TEST_CASE("Base256: subtraction") { +TEST_CASE("Base256: 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)); + } + + SECTION("Subtract to zero") { + REQUIRE(make(123456) - make(123456) == make(0)); + REQUIRE(make(MAX_U64) - make(MAX_U64) == make(0)); + } + SECTION("Borrow across one byte") { REQUIRE(make(256) - make(1) == make(255)); } - SECTION("Borrow across multiple bytes") { + SECTION("Cascading borrow across multiple bytes") { REQUIRE(make(65536) - make(1) == make(65535)); + REQUIRE(make(16777216) - make(1) == make(16777215)); + REQUIRE(make(4294967296) - make(1) == make(4294967295)); } - SECTION("Subtract to zero") { - REQUIRE(make(123456) - make(123456) == make(0)); + SECTION("Negative result clamping (Underflow to 0)") { + // Based on your comment: "if it would be negative, 0 is returned" + REQUIRE(make(10) - make(20) == make(0)); + REQUIRE(make(0) - make(1) == make(0)); + REQUIRE(make(500) - make(MAX_U64) == make(0)); } - SECTION("Subtract zero is identity") { - REQUIRE(make(123456) - make(0) == make(123456)); + SECTION("Compound -= operator") { + Base256 a(100); + a -= make(40); + REQUIRE(a == make(60)); + a -= make(60); + REQUIRE(a == make(0)); } - - // Many big-int libs clamp or assume a >= b. Here we skip a 0)") { - for (uint64_t a = 1; a <= 50; ++a) - for (uint64_t b = 1; b <= 50; ++b) - for (uint64_t c = 1; c <= 50; ++c) { + 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); REQUIRE(divisionResult == additionResult); } } + + SECTION("Chained arithmetic: ((100 * 256) + 50 - 25) / 5 == 5125") { + Base256 result = ((make(100) * make(256)) + make(50) - make(25)) / make(5); + REQUIRE(result == make(5125)); + } } -TEST_CASE("Base256: comparisons") { +TEST_CASE("Base256: comparisons edge cases") { Base256 a0(0), a1(1), a2(2), a255(255), a256(256); + Base256 max64(MAX_U64); SECTION("Basic ordering") { REQUIRE(a0 < a1); @@ -174,17 +255,22 @@ TEST_CASE("Base256: comparisons") { REQUIRE(a1 > a0); } - SECTION("Equality vs ordering") { + SECTION("Equality vs ordering on same values") { Base256 x(123456); Base256 y(123456); REQUIRE(x == y); REQUIRE_FALSE(x != y); - // Strictness: x < x must be false, x <= x must be true + // Strictness REQUIRE_FALSE(x < x); REQUIRE(x <= x); REQUIRE_FALSE(x > x); REQUIRE(x >= x); + + REQUIRE_FALSE(x < y); + REQUIRE(x <= y); + REQUIRE_FALSE(x > y); + REQUIRE(x >= y); } SECTION("Cross-byte comparisons") { @@ -192,5 +278,15 @@ TEST_CASE("Base256: comparisons") { REQUIRE(make(65536) > make(65535)); REQUIRE(make(65536) >= make(65536)); REQUIRE_FALSE(make(65536) < make(65536)); + REQUIRE(make(16777216) > make(16777215)); } -} + + SECTION("Extremely large comparisons") { + REQUIRE(max64 > a256); + REQUIRE(a0 < max64); + + Base256 overflow = max64 + a1; // Exceeds uint64_t + REQUIRE(overflow > max64); + REQUIRE(max64 < overflow); + } +} \ No newline at end of file diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index fe2570c..86feb1d 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -53,6 +53,11 @@ void operations::Base256::add(const std::vector &b) noexcept { [[nodiscard]] std::vector operations::Base256::sub( const std::vector &a, const std::vector &b) noexcept { + // Safely clamp to 0 if the number being subtracted is larger than the base + if (isBigger(b, a)) { + return {0}; + } + std::vector result; // Handle an underflow when subtracting From a1224268529f3df5c72057f8253f505e6d84fffd Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 29 Apr 2026 14:49:13 +0200 Subject: [PATCH 14/42] first doc draft --- src/docs/base256.md | 81 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/docs/base256.md diff --git a/src/docs/base256.md b/src/docs/base256.md new file mode 100644 index 0000000..840a2e5 --- /dev/null +++ b/src/docs/base256.md @@ -0,0 +1,81 @@ +# 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. + +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 & Subtraction (`O(N)`) +These operations execute in linear time, sweeping from index `0` upwards. +* **Addition:** Evaluates `a[i] + b[i] + carry` using a `uint16_t` buffer. If the sum exceeds `0xFF` (255), the bitwise shift `sum >> 8` extracts the carry, and the modulo `sum & 0xFF` is pushed to the result. +* **Subtraction:** Subtracts byte-by-byte. If `a[i] < b[i]`, a `borrow` is triggered, adding `256` to the current byte and subtracting `1` from the subsequent index. Because the class represents unsigned rings, any subtraction resulting in a global negative state is actively intercepted and clamped strictly to `0`. + +### Multiplication (`O(N * M)`) +Multiplication implements a double-loop accumulator matrix. It guarantees no reallocation overhead by pre-allocating a vector of size `A.size() + B.size()`—the maximum possible magnitude of a product. It iterates through every byte of `A`, multiplies it by every byte of `B`, and seamlessly propagates 16-bit carries up the pre-allocated vector indices. + +### Division (`O(Bits)`) +To avoid the disastrous performance of iterative subtraction, division utilizes highly optimized **Bitwise Long Division**. +It calculates the exact highest active bit (`getStartBitIndex`), shifting a scoped `dividendMask` over the divisor one bit at a time. If the mask is larger than the divisor, the divisor is subtracted and the corresponding bit in the newly constructed `quotient` vector is toggled to `1`. + +--- + +## 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 From 20613919e5c777470aa4b34821922d6e85074ba5 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 30 Apr 2026 11:54:40 +0200 Subject: [PATCH 15/42] add new workflow --- .github/workflows/ci.yml | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2f35455 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI/CD Pipeline + +on: + # Triggers when a Pull Request is opened or updated + pull_request: + branches: [ "main" ] + # Triggers when a push is made directly to the main branch + push: + branches: [ "main" ] + +jobs: + build-and-test: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Configure CMake + # Configure in Release mode. + # BUILD_TESTING is ON by default in your CMakeLists. + run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + + - name: Build Project + # Uses all available cores to speed up compilation + run: cmake --build build --config Release -j $(nproc) + + - name: Run Tests + # Runs CTest inside the build directory. + # --output-on-failure ensures test errors are printed to the logs. + run: ctest --test-dir build --output-on-failure -C Release + + # Optional: Temporarily store the compiled executable as an artifact + # so it can be attached to the GitHub Release in the next job. + - name: Upload Executable Artifact + uses: actions/upload-artifact@v4 + with: + name: RSA-Encryptor-Linux + path: build/RSA-Encryptor + retention-days: 1 + + create-prerelease: + name: Create Pre-Release + needs: build-and-test + # This ensures the release job ONLY runs on pushes to main (not on PRs) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + + # Required permission to create tags and releases + permissions: + contents: write + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Download Executable Artifact + uses: actions/download-artifact@v4 + with: + name: RSA-Encryptor-Linux + path: ./release-artifacts + + - name: Generate Tag Name + id: generate_tag + run: | + # Get current date in YYYYMMDD format + DATE=$(date +'%Y%m%d') + # Get the first 8 characters of the commit hash + SHORT_SHA=${GITHUB_SHA::8} + # Combine them + TAG_NAME="dev-${DATE}-${SHORT_SHA}" + + echo "Created Tag Name: $TAG_NAME" + # Save it to GitHub Environment Variables so the next step can use it + echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV + + - name: Publish GitHub Pre-Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Uses the pre-installed GitHub CLI to create the release and upload the executable. + run: | + gh release create "${{ env.TAG_NAME }}" ./release-artifacts/RSA-Encryptor \ + --title "Development Build ${{ env.TAG_NAME }}" \ + --prerelease \ + --generate-notes \ No newline at end of file From d69439194e01ddc87c1ed178cf7d439177ce86c9 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 30 Apr 2026 12:31:50 +0200 Subject: [PATCH 16/42] format --- src/CMakeLists.txt | 4 ++-- src/cli.cpp | 6 ++++-- src/cli.h | 18 ++++++++++-------- src/encryption.h | 6 +++--- src/key.cpp | 26 +++++++++++++++----------- src/key.h | 11 ++++++++--- src/main.cpp | 3 +-- src/tests/test_base256.cpp | 4 ++-- src/utility.h | 8 +++++--- 9 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 22367c0..add70c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -39,7 +39,7 @@ add_executable(RSA-Encryptor main.cpp cli.cpp) target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) # Tests -if(BUILD_TESTING) +if (BUILD_TESTING) add_executable(RSA-Encryptor-Tests tests/test_base256.cpp ) @@ -54,4 +54,4 @@ if(BUILD_TESTING) TEST_PREFIX base256: WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) -endif() \ No newline at end of file +endif () \ No newline at end of file diff --git a/src/cli.cpp b/src/cli.cpp index 027a4db..27d23ce 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -8,6 +8,8 @@ void cli::help() { std::cout << "Welcome to RSA-Encryptor" << std::endl; } void cli::keyManager::create() { key::createRSAKey(); } -void cli::keyManager::list() {} +void cli::keyManager::list() { +} -void cli::keyManager::print(const std::string& name, bool publicKey, bool privateKey) {} +void cli::keyManager::print(const std::string &name, bool publicKey, bool privateKey) { +} diff --git a/src/cli.h b/src/cli.h index f56e569..76be957 100644 --- a/src/cli.h +++ b/src/cli.h @@ -4,13 +4,15 @@ #include namespace cli { -void help(); + void help(); -namespace keyManager { -void create(); -void list(); -void print(const std::string& name, bool publicKey, bool privateKey); -} // namespace keyManager -} // namespace cli + namespace keyManager { + void create(); -#endif \ No newline at end of file + void list(); + + void print(const std::string &name, bool publicKey, bool privateKey); + } // namespace keyManager +} // namespace cli + +#endif diff --git a/src/encryption.h b/src/encryption.h index 111cf26..14184e8 100644 --- a/src/encryption.h +++ b/src/encryption.h @@ -5,9 +5,9 @@ #include class encryption { - private: - public: +private: +public: std::int8_t encrypt(std::uint8_t data, unsigned long int e, unsigned long N); }; -#endif \ No newline at end of file +#endif diff --git a/src/key.cpp b/src/key.cpp index 0c0e6ec..b6717a3 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -22,7 +22,7 @@ int key::keyExists(std::string name) { std::filesystem::path keysFolder = keysPath(); // go over every file in the KEY_FOLDER - for (const auto &entry : std::filesystem::directory_iterator(keysFolder)) { + for (const auto &entry: std::filesystem::directory_iterator(keysFolder)) { // check if the name matches if (entry.path().stem().string() == name) { // check if it's a public key @@ -141,7 +141,7 @@ std::string key::base64Encode(const std::vector &data) { std::string outString; // convert the numeric value to the corresponding char of the base64 charset - for (auto c : result) { + for (auto c: result) { outString += base64Chars[c]; } @@ -171,7 +171,7 @@ std::vector key::base64Decode(std::string data) { // check for valid char code if (number > 0b111111) { std::cerr << "trying to Decode not valid char: " << letterSegment.at(i) - << std::endl; + << std::endl; continue; } dataSegment += number << i * 6; @@ -196,14 +196,18 @@ uint8_t key::getBase64Index(char letter) { } int key::createKey(std::vector *keyPublic, std::vector *keyPrivate) { - *keyPublic = {23, 87, 45, 190, 12, 78, 34, 210, 56, 89, 123, 67, 90, 150, 32, 76, 54, - 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, 78, 201, - 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, 170, 53, 95, 41}; - *keyPrivate = {34, 78, 123, 56, 89, 210, 45, 190, 12, 87, 67, 150, 32, 76, 54, - 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, - 78, 201, 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, - 170, 53, 95, 102, 147, 68, 191, 44, 85, 30, 177, 59, 93, 112, 135, - 79, 202, 16, 85, 40, 221, 67, 98, 23, 87, 91, 65}; + *keyPublic = { + 23, 87, 45, 190, 12, 78, 34, 210, 56, 89, 123, 67, 90, 150, 32, 76, 54, + 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, 78, 201, + 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, 170, 53, 95, 41 + }; + *keyPrivate = { + 34, 78, 123, 56, 89, 210, 45, 190, 12, 87, 67, 150, 32, 76, 54, + 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, + 78, 201, 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, + 170, 53, 95, 102, 147, 68, 191, 44, 85, 30, 177, 59, 93, 112, 135, + 79, 202, 16, 85, 40, 221, 67, 98, 23, 87, 91, 65 + }; return 1; } diff --git a/src/key.h b/src/key.h index 005a59d..5b6335c 100644 --- a/src/key.h +++ b/src/key.h @@ -13,26 +13,31 @@ enum { NONE, PUBLIC, PRIVATE, BOTH }; const char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; class key { - private: +private: static std::filesystem::path keysPath(); + static int keyExists(std::string name); + static void generatePublicFromPrivate(); static int writeKey(const std::string &name, std::vector *data, bool isPublic); + static int readKey(const std::string &name, std::vector *data, bool isPublic); static std::string base64Encode(const std::vector &data); + static std::vector base64Decode(std::string data); static uint8_t getBase64Index(char letter); static int createKey(std::vector *keyPublic, std::vector *keyPrivate); - public: +public: static void createRSAKey(); static std::vector *getPrivateKey(std::string &name); + static std::vector *getPublicKey(std::string &name); }; -#endif \ No newline at end of file +#endif diff --git a/src/main.cpp b/src/main.cpp index ab31a03..0723b35 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ #include "utility.h" #include "vec/operations.h" -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { struct { bool privateKey = false; bool publicKey = false; @@ -54,7 +54,6 @@ int main(int argc, char* argv[]) { } else if (subFeature == "print" && argv[3] != nullptr) { cli::keyManager::print(argv[3], arguments.publicKey, arguments.privateKey); } - } else if (feature == "help") { cli::help(); } else { diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index 7cd254c..e84358a 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -45,7 +45,7 @@ TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { SECTION("Self-assignment is safe") { Base256 a(424242); - Base256& ref = a; + Base256 &ref = a; a = ref; // self-assign REQUIRE(a == make(424242)); } @@ -289,4 +289,4 @@ TEST_CASE("Base256: comparisons edge cases") { REQUIRE(overflow > max64); REQUIRE(max64 < overflow); } -} \ No newline at end of file +} diff --git a/src/utility.h b/src/utility.h index 5cae06c..7de0f4d 100644 --- a/src/utility.h +++ b/src/utility.h @@ -4,11 +4,13 @@ #include class utility { - private: - public: +private: +public: int gcd(int a, int b); + int phi(int a, int b); + bool checkForPrime(int number); }; -#endif \ No newline at end of file +#endif From e545833a323b68235116bfb5aea430d3f235b290 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 30 Apr 2026 12:32:01 +0200 Subject: [PATCH 17/42] more format --- src/vec/helper.h | 12 +-- src/vec/operations.cpp | 3 +- src/vec/operations.h | 234 +++++++++++++++++++++-------------------- 3 files changed, 125 insertions(+), 124 deletions(-) diff --git a/src/vec/helper.h b/src/vec/helper.h index 87d3a4c..ce724f5 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -8,7 +8,7 @@ std::int64_t index = 0; std::int64_t bitsSince1 = 0; - for (const std::uint8_t number : a) { + for (const std::uint8_t number: a) { if (number == 0) { bitsSince1 += 8; continue; @@ -54,7 +54,7 @@ bool mostSignificantBit = (pickNumber[index / 8] & (0b1 << (index % 8))) != 0; - for (std::uint8_t currentByte : number) { + for (std::uint8_t currentByte: number) { // FIX: The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets overwritten/shifted. bool nextMSB = (currentByte & 0x80) != 0; result.push_back(static_cast((currentByte << 1) | mostSignificantBit)); @@ -67,7 +67,7 @@ } [[nodiscard]] inline bool isBigger(const std::vector &a, - const std::vector &b) noexcept { + const std::vector &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; @@ -87,7 +87,7 @@ } [[nodiscard]] inline bool isEqual(const std::vector &a, - const std::vector &b) noexcept { + const std::vector &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; @@ -107,10 +107,10 @@ } [[nodiscard]] inline bool isZero(const std::vector &a) { - for (const std::uint8_t number : a) { + for (const std::uint8_t number: a) { if (number != 0) return false; } return true; } -#endif // RSA_ENCRYPTOR_HELPER_H \ No newline at end of file +#endif // RSA_ENCRYPTOR_HELPER_H diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index 86feb1d..5dd9a77 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -52,7 +52,6 @@ void operations::Base256::add(const std::vector &b) noexcept { [[nodiscard]] std::vector operations::Base256::sub( const std::vector &a, const std::vector &b) noexcept { - // Safely clamp to 0 if the number being subtracted is larger than the base if (isBigger(b, a)) { return {0}; @@ -207,4 +206,4 @@ void operations::Base256::div(const std::vector &divisor, } return result; -} \ No newline at end of file +} diff --git a/src/vec/operations.h b/src/vec/operations.h index 626af31..b77f74f 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -8,121 +8,123 @@ #include "helper.h" namespace operations { -class Base256 { - public: - - Base256(const std::uint64_t initialValue) { - data = convertToVector(initialValue); - } - - Base256(const Base256& base256) { - data = base256.data; - } - - Base256() { - data = convertToVector(0); - } - - void add(const std::vector &b) noexcept; - void sub(const std::vector &b) noexcept; - std::vector sub(const std::vector &a, - const std::vector &b) noexcept; - void mul(const std::vector &b) noexcept; - void div(const std::vector &divisor, - std::vector *remaining = nullptr) noexcept; - - std::vector pow(const std::vector &a, - const std::uint64_t &pow) noexcept; - - void print() const { - std::uint64_t value = 0; - // Vector stores data natively as little-endian, iterate using reverse iterator - for (auto it = data.rbegin(); it != data.rend(); ++it) { - value = (value << 8) | static_cast(*it); - } - std::cout << value << '\n'; - } - - Base256& operator=(const Base256& other) { - // We protect us from self assignment - if (this != &other) { - data = other.data; - } - return *this; - } - - friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result += lhs; - return result; - } - - Base256 &operator+=(const Base256 &rhs) { - add(rhs.data); - return *this; - } - - friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result -= lhs; - return result; - } - - Base256 &operator-=(const Base256 &rhs) { - sub(rhs.data); - return *this; - } - - friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result *= lhs; - return result; - } - - Base256 &operator*=(const Base256 &rhs) { - mul(rhs.data); - return *this; - } - - friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result /= lhs; - return result; - } - - Base256 &operator/=(const Base256 &rhs) { - div(rhs.data); - return *this; - } - - [[nodiscard]] friend bool operator==(const Base256& lhs, const Base256& rhs) { - return isEqual(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator!=(const Base256& lhs, const Base256& rhs) { - return !isEqual(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator>(const Base256& lhs, const Base256& rhs) { - return isBigger(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator<(const Base256& lhs, const Base256& rhs) { - return isBigger(rhs.data, lhs.data); - } - - [[nodiscard]] friend bool operator>=(const Base256& lhs, const Base256& rhs) { - return !isBigger(rhs.data, lhs.data); - } - - [[nodiscard]] friend bool operator<=(const Base256& lhs, const Base256& rhs) { - return !isBigger(lhs.data, rhs.data); - } - - private: - std::vector data; -}; - -} // namespace operations + class Base256 { + public: + Base256(const std::uint64_t initialValue) { + data = convertToVector(initialValue); + } + + Base256(const Base256 &base256) { + data = base256.data; + } + + Base256() { + data = convertToVector(0); + } + + void add(const std::vector &b) noexcept; + + void sub(const std::vector &b) noexcept; + + std::vector sub(const std::vector &a, + const std::vector &b) noexcept; + + void mul(const std::vector &b) noexcept; + + void div(const std::vector &divisor, + std::vector *remaining = nullptr) noexcept; + + std::vector pow(const std::vector &a, + const std::uint64_t &pow) noexcept; + + void print() const { + std::uint64_t value = 0; + // Vector stores data natively as little-endian, iterate using reverse iterator + for (auto it = data.rbegin(); it != data.rend(); ++it) { + value = (value << 8) | static_cast(*it); + } + std::cout << value << '\n'; + } + + Base256 &operator=(const Base256 &other) { + // We protect us from self assignment + if (this != &other) { + data = other.data; + } + return *this; + } + + friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result += lhs; + return result; + } + + Base256 &operator+=(const Base256 &rhs) { + add(rhs.data); + return *this; + } + + friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result -= lhs; + return result; + } + + Base256 &operator-=(const Base256 &rhs) { + sub(rhs.data); + return *this; + } + + friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result *= lhs; + return result; + } + + Base256 &operator*=(const Base256 &rhs) { + mul(rhs.data); + return *this; + } + + friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result /= lhs; + return result; + } + + Base256 &operator/=(const Base256 &rhs) { + div(rhs.data); + return *this; + } + + [[nodiscard]] friend bool operator==(const Base256 &lhs, const Base256 &rhs) { + return isEqual(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator!=(const Base256 &lhs, const Base256 &rhs) { + return !isEqual(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator>(const Base256 &lhs, const Base256 &rhs) { + return isBigger(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator<(const Base256 &lhs, const Base256 &rhs) { + return isBigger(rhs.data, lhs.data); + } + + [[nodiscard]] friend bool operator>=(const Base256 &lhs, const Base256 &rhs) { + return !isBigger(rhs.data, lhs.data); + } + + [[nodiscard]] friend bool operator<=(const Base256 &lhs, const Base256 &rhs) { + return !isBigger(lhs.data, rhs.data); + } + + private: + std::vector data; + }; +} // namespace operations #endif From 7a18e490b1c99c70e1609d149288f2e276c8a58e Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Thu, 30 Apr 2026 12:40:11 +0200 Subject: [PATCH 18/42] simplify workflows --- .github/workflows/build.yml | 36 ------------ .github/workflows/ci.yml | 92 +++++++++++++++++-------------- .github/workflows/release-tag.yml | 71 ------------------------ 3 files changed, 50 insertions(+), 149 deletions(-) delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/release-tag.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 2b7a58d..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,36 +0,0 @@ -# It builds the project on multiple operating systems (Ubuntu, Windows, and macOS) - -name: Build Project - -on: - pull_request: - branches: - - main - -jobs: - debug: - name: Build - runs-on: ${{ matrix.config.os }} - strategy: - 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: Build Project - uses: threeal/cmake-action@v2.1.0 - with: - args: -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} - c-compiler: ${{ matrix.config.cc }} - cxx-compiler: ${{ matrix.config.cxx }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f35455..4689a0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,86 +1,94 @@ name: CI/CD Pipeline on: - # Triggers when a Pull Request is opened or updated pull_request: branches: [ "main" ] - # Triggers when a push is made directly to the main branch 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 and Test - runs-on: ubuntu-latest + 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 Code + - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Ninja + uses: ashutoshvarma/setup-ninja@master - name: Configure CMake - # Configure in Release mode. - # BUILD_TESTING is ON by default in your CMakeLists. - run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + 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 - # Uses all available cores to speed up compilation - run: cmake --build build --config Release -j $(nproc) + 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 - - name: Run Tests - # Runs CTest inside the build directory. - # --output-on-failure ensures test errors are printed to the logs. - run: ctest --test-dir build --output-on-failure -C Release + # 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' - # Optional: Temporarily store the compiled executable as an artifact - # so it can be attached to the GitHub Release in the next job. - - name: Upload Executable Artifact + - name: Upload Artifacts for Release + if: matrix.build-configuration == 'Release' uses: actions/upload-artifact@v4 with: - name: RSA-Encryptor-Linux - path: build/RSA-Encryptor + name: ${{ matrix.config.os }}-build + path: ${{ matrix.config.os }}-build.zip retention-days: 1 - create-prerelease: + + # JOB 2: Create Pre-Release + # Runs only on pushes to main, after tests pass + release: name: Create Pre-Release needs: build-and-test - # This ensures the release job ONLY runs on pushes to main (not on PRs) if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest - - # Required permission to create tags and releases permissions: contents: write - steps: - name: Checkout Code uses: actions/checkout@v4 - - name: Download Executable Artifact + - name: Download all OS Artifacts uses: actions/download-artifact@v4 with: - name: RSA-Encryptor-Linux path: ./release-artifacts + merge-multiple: true # Puts the Mac, Win, and Linux zip files in the same folder - - name: Generate Tag Name - id: generate_tag + - name: Generate Tag & Publish Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # Get current date in YYYYMMDD format + # Create naming convention DATE=$(date +'%Y%m%d') - # Get the first 8 characters of the commit hash SHORT_SHA=${GITHUB_SHA::8} - # Combine them TAG_NAME="dev-${DATE}-${SHORT_SHA}" - echo "Created Tag Name: $TAG_NAME" - # Save it to GitHub Environment Variables so the next step can use it - echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV - - - name: Publish GitHub Pre-Release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Uses the pre-installed GitHub CLI to create the release and upload the executable. - run: | - gh release create "${{ env.TAG_NAME }}" ./release-artifacts/RSA-Encryptor \ - --title "Development Build ${{ env.TAG_NAME }}" \ + # 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/release-tag.yml b/.github/workflows/release-tag.yml deleted file mode 100644 index 1f19943..0000000 --- a/.github/workflows/release-tag.yml +++ /dev/null @@ -1,71 +0,0 @@ -# This workflow runs when a tag is pushed to the repository point to the main branch. -# It builds the project on multiple operating systems (Ubuntu, Windows, and macOS) and -# creates a pre-release. - -name: Create Release - -on: - push: - tags: - - 'v*' - -jobs: - release: - name: Create Release - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: true - - build: - name: Build Project - needs: release - runs-on: ${{ matrix.config.os }} - strategy: - 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: [ Release ] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Ninja - uses: ashutoshvarma/setup-ninja@master - - - name: Build Project - uses: threeal/cmake-action@v2.1.0 - with: - args: -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.build-configuration }} - c-compiler: ${{ matrix.config.cc }} - cxx-compiler: ${{ matrix.config.cxx }} - - - name: Compress Build Directory - uses: thedoctor0/zip-release@0.7.5 - with: - type: 'zip' - path: 'build' - filename: '${{ matrix.config.os}}-build.zip' - - - name: Upload Release Asset - uses: alexellis/upload-assets@0.4.0 - env: - GITHUB_TOKEN: ${{ github.token }} - with: - asset_paths: '["${{ matrix.config.os}}-build.zip"]' \ No newline at end of file From 274afd8d0f21f1eea1638d5e9eec4bd5293fbeb6 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Mon, 4 May 2026 11:01:12 +0200 Subject: [PATCH 19/42] format --- src/cli.cpp | 6 +- src/cli.h | 14 +-- src/encryption.h | 4 +- src/key.h | 4 +- src/tests/test_base256.cpp | 45 +++----- src/utility.h | 4 +- src/vec/helper.h | 15 +-- src/vec/operations.h | 220 ++++++++++++++++++------------------- 8 files changed, 147 insertions(+), 165 deletions(-) diff --git a/src/cli.cpp b/src/cli.cpp index 27d23ce..bcae579 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -8,8 +8,6 @@ void cli::help() { std::cout << "Welcome to RSA-Encryptor" << std::endl; } void cli::keyManager::create() { key::createRSAKey(); } -void cli::keyManager::list() { -} +void cli::keyManager::list() {} -void cli::keyManager::print(const std::string &name, bool publicKey, bool privateKey) { -} +void cli::keyManager::print(const std::string &name, bool publicKey, bool privateKey) {} diff --git a/src/cli.h b/src/cli.h index 76be957..9739ebc 100644 --- a/src/cli.h +++ b/src/cli.h @@ -4,15 +4,15 @@ #include namespace cli { - void help(); +void help(); - namespace keyManager { - void create(); +namespace keyManager { +void create(); - void list(); +void list(); - void print(const std::string &name, bool publicKey, bool privateKey); - } // namespace keyManager -} // namespace cli +void print(const std::string &name, bool publicKey, bool privateKey); +} // namespace keyManager +} // namespace cli #endif diff --git a/src/encryption.h b/src/encryption.h index 14184e8..deee868 100644 --- a/src/encryption.h +++ b/src/encryption.h @@ -5,8 +5,8 @@ #include class encryption { -private: -public: + private: + public: std::int8_t encrypt(std::uint8_t data, unsigned long int e, unsigned long N); }; diff --git a/src/key.h b/src/key.h index 5b6335c..dd697ed 100644 --- a/src/key.h +++ b/src/key.h @@ -13,7 +13,7 @@ enum { NONE, PUBLIC, PRIVATE, BOTH }; const char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; class key { -private: + private: static std::filesystem::path keysPath(); static int keyExists(std::string name); @@ -32,7 +32,7 @@ class key { static int createKey(std::vector *keyPublic, std::vector *keyPrivate); -public: + public: static void createRSAKey(); static std::vector *getPrivateKey(std::string &name); diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index e84358a..9613d5e 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -1,7 +1,7 @@ #include #include -#include #include +#include #include "vec/operations.h" @@ -46,7 +46,7 @@ TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { SECTION("Self-assignment is safe") { Base256 a(424242); Base256 &ref = a; - a = ref; // self-assign + a = ref; // self-assign REQUIRE(a == make(424242)); } } @@ -60,27 +60,23 @@ TEST_CASE("Base256: equality and inequality") { } TEST_CASE("Base256: addition & compound addition") { - SECTION("Simple add") { - REQUIRE(make(10) + make(20) == make(30)); - } + SECTION("Simple add") { REQUIRE(make(10) + make(20) == make(30)); } SECTION("Add zero is identity") { REQUIRE(make(123456) + make(0) == make(123456)); REQUIRE(make(0) + make(123456) == make(123456)); } - SECTION("Carry across one byte") { - REQUIRE(make(255) + make(1) == make(256)); - } + SECTION("Carry across one byte") { REQUIRE(make(255) + make(1) == make(256)); } SECTION("Cascading carry across multiple bytes") { - REQUIRE(make(65535) + make(1) == make(65536)); // 0xFFFF + 1 = 0x10000 - REQUIRE(make(16777215) + make(1) == make(16777216)); // 0xFFFFFF + 1 - REQUIRE(make(4294967295) + make(1) == make(4294967296)); // 0xFFFFFFFF + 1 + REQUIRE(make(65535) + make(1) == make(65536)); // 0xFFFF + 1 = 0x10000 + REQUIRE(make(16777215) + make(1) == make(16777216)); // 0xFFFFFF + 1 + REQUIRE(make(4294967295) + make(1) == make(4294967296)); // 0xFFFFFFFF + 1 } SECTION("Addition exceeding uint64_t (8 bytes to 9 bytes)") { - Base256 a(MAX_U64); // 0xFFFFFFFFFFFFFFFF + Base256 a(MAX_U64); // 0xFFFFFFFFFFFFFFFF Base256 b(2); Base256 c = a + b; @@ -108,22 +104,16 @@ TEST_CASE("Base256: addition & compound addition") { } TEST_CASE("Base256: subtraction & compound subtraction") { - SECTION("Simple sub") { - REQUIRE(make(30) - make(20) == make(10)); - } + SECTION("Simple sub") { REQUIRE(make(30) - make(20) == make(10)); } - SECTION("Subtract zero is identity") { - REQUIRE(make(123456) - make(0) == make(123456)); - } + SECTION("Subtract zero is identity") { REQUIRE(make(123456) - make(0) == make(123456)); } SECTION("Subtract to zero") { REQUIRE(make(123456) - make(123456) == make(0)); REQUIRE(make(MAX_U64) - make(MAX_U64) == make(0)); } - SECTION("Borrow across one byte") { - REQUIRE(make(256) - make(1) == make(255)); - } + SECTION("Borrow across one byte") { REQUIRE(make(256) - make(1) == make(255)); } SECTION("Cascading borrow across multiple bytes") { REQUIRE(make(65536) - make(1) == make(65535)); @@ -148,9 +138,7 @@ TEST_CASE("Base256: subtraction & compound subtraction") { } TEST_CASE("Base256: multiplication & compound multiplication") { - SECTION("Simple mul") { - REQUIRE(make(7) * make(6) == make(42)); - } + SECTION("Simple mul") { REQUIRE(make(7) * make(6) == make(42)); } SECTION("Mul by zero and one") { REQUIRE(make(123456) * make(0) == make(0)); @@ -165,9 +153,9 @@ TEST_CASE("Base256: multiplication & compound multiplication") { } SECTION("Large multiplication (Exceeding uint64_t)") { - Base256 a(4294967295); // 0xFFFFFFFF (4 bytes) + Base256 a(4294967295); // 0xFFFFFFFF (4 bytes) Base256 b(4294967295); - Base256 c = a * b; // Should be 0xFFFFFFFE00000001 (8 bytes) + Base256 c = a * b; // Should be 0xFFFFFFFE00000001 (8 bytes) // Verify via division REQUIRE(c / a == b); @@ -186,7 +174,8 @@ TEST_CASE("Base256: multiplication & compound multiplication") { for (uint64_t a = 0; a <= 10; ++a) for (uint64_t b = 0; b <= 10; ++b) for (uint64_t c = 0; c <= 10; ++c) - REQUIRE(make(a) * (make(b) + make(c)) == (make(a) * make(b)) + (make(a) * make(c))); + REQUIRE(make(a) * (make(b) + make(c)) == + (make(a) * make(b)) + (make(a) * make(c))); } } @@ -285,7 +274,7 @@ TEST_CASE("Base256: comparisons edge cases") { REQUIRE(max64 > a256); REQUIRE(a0 < max64); - Base256 overflow = max64 + a1; // Exceeds uint64_t + Base256 overflow = max64 + a1; // Exceeds uint64_t REQUIRE(overflow > max64); REQUIRE(max64 < overflow); } diff --git a/src/utility.h b/src/utility.h index 7de0f4d..f88ca28 100644 --- a/src/utility.h +++ b/src/utility.h @@ -4,8 +4,8 @@ #include class utility { -private: -public: + private: + public: int gcd(int a, int b); int phi(int a, int b); diff --git a/src/vec/helper.h b/src/vec/helper.h index ce724f5..39979e8 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -8,7 +8,7 @@ std::int64_t index = 0; std::int64_t bitsSince1 = 0; - for (const std::uint8_t number: a) { + for (const std::uint8_t number : a) { if (number == 0) { bitsSince1 += 8; continue; @@ -27,8 +27,7 @@ return index - 1; } -[[nodiscard]] inline std::vector convertToVector( - std::uint64_t number) noexcept { +[[nodiscard]] inline std::vector convertToVector(std::uint64_t number) noexcept { std::vector result; // Securely ensure 0 results in at least [0], never[] @@ -54,8 +53,9 @@ bool mostSignificantBit = (pickNumber[index / 8] & (0b1 << (index % 8))) != 0; - for (std::uint8_t currentByte: number) { - // FIX: The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets overwritten/shifted. + for (std::uint8_t currentByte : number) { + // FIX: The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets + // overwritten/shifted. bool nextMSB = (currentByte & 0x80) != 0; result.push_back(static_cast((currentByte << 1) | mostSignificantBit)); mostSignificantBit = nextMSB; @@ -79,7 +79,8 @@ if (aValue > bValue) { return true; } else if (aValue < bValue) { - return false; // FIX: Ensure false is explicitly returned if the checking bit drops behind. + return false; // FIX: Ensure false is explicitly returned if the checking bit drops + // behind. } } @@ -107,7 +108,7 @@ } [[nodiscard]] inline bool isZero(const std::vector &a) { - for (const std::uint8_t number: a) { + for (const std::uint8_t number : a) { if (number != 0) return false; } return true; diff --git a/src/vec/operations.h b/src/vec/operations.h index b77f74f..c93d304 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -8,123 +8,117 @@ #include "helper.h" namespace operations { - class Base256 { - public: - Base256(const std::uint64_t initialValue) { - data = convertToVector(initialValue); - } - - Base256(const Base256 &base256) { - data = base256.data; - } - - Base256() { - data = convertToVector(0); - } - - void add(const std::vector &b) noexcept; - - void sub(const std::vector &b) noexcept; - - std::vector sub(const std::vector &a, - const std::vector &b) noexcept; - - void mul(const std::vector &b) noexcept; - - void div(const std::vector &divisor, - std::vector *remaining = nullptr) noexcept; +class Base256 { + public: + Base256(const std::uint64_t initialValue) { data = convertToVector(initialValue); } - std::vector pow(const std::vector &a, - const std::uint64_t &pow) noexcept; + Base256(const Base256 &base256) { data = base256.data; } - void print() const { - std::uint64_t value = 0; - // Vector stores data natively as little-endian, iterate using reverse iterator - for (auto it = data.rbegin(); it != data.rend(); ++it) { - value = (value << 8) | static_cast(*it); - } - std::cout << value << '\n'; - } - - Base256 &operator=(const Base256 &other) { - // We protect us from self assignment - if (this != &other) { - data = other.data; - } - return *this; - } - - friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result += lhs; - return result; - } + Base256() { data = convertToVector(0); } - Base256 &operator+=(const Base256 &rhs) { - add(rhs.data); - return *this; - } - - friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result -= lhs; - return result; - } - - Base256 &operator-=(const Base256 &rhs) { - sub(rhs.data); - return *this; - } + void add(const std::vector &b) noexcept; - friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result *= lhs; - return result; - } - - Base256 &operator*=(const Base256 &rhs) { - mul(rhs.data); - return *this; - } - - friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { - Base256 result(rhs); - result /= lhs; - return result; - } - - Base256 &operator/=(const Base256 &rhs) { - div(rhs.data); - return *this; - } - - [[nodiscard]] friend bool operator==(const Base256 &lhs, const Base256 &rhs) { - return isEqual(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator!=(const Base256 &lhs, const Base256 &rhs) { - return !isEqual(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator>(const Base256 &lhs, const Base256 &rhs) { - return isBigger(lhs.data, rhs.data); - } - - [[nodiscard]] friend bool operator<(const Base256 &lhs, const Base256 &rhs) { - return isBigger(rhs.data, lhs.data); - } - - [[nodiscard]] friend bool operator>=(const Base256 &lhs, const Base256 &rhs) { - return !isBigger(rhs.data, lhs.data); - } - - [[nodiscard]] friend bool operator<=(const Base256 &lhs, const Base256 &rhs) { - return !isBigger(lhs.data, rhs.data); - } + void sub(const std::vector &b) noexcept; + + std::vector sub(const std::vector &a, + const std::vector &b) noexcept; + + void mul(const std::vector &b) noexcept; + + void div(const std::vector &divisor, + std::vector *remaining = nullptr) noexcept; + + std::vector pow(const std::vector &a, + const std::uint64_t &pow) noexcept; + + void print() const { + std::uint64_t value = 0; + // Vector stores data natively as little-endian, iterate using reverse iterator + for (auto it = data.rbegin(); it != data.rend(); ++it) { + value = (value << 8) | static_cast(*it); + } + std::cout << value << '\n'; + } + + Base256 &operator=(const Base256 &other) { + // We protect us from self assignment + if (this != &other) { + data = other.data; + } + return *this; + } + + friend Base256 operator+(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result += lhs; + return result; + } + + Base256 &operator+=(const Base256 &rhs) { + add(rhs.data); + return *this; + } + + friend Base256 operator-(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result -= lhs; + return result; + } + + Base256 &operator-=(const Base256 &rhs) { + sub(rhs.data); + return *this; + } + + friend Base256 operator*(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result *= lhs; + return result; + } + + Base256 &operator*=(const Base256 &rhs) { + mul(rhs.data); + return *this; + } + + friend Base256 operator/(const Base256 &rhs, const Base256 &lhs) { + Base256 result(rhs); + result /= lhs; + return result; + } + + Base256 &operator/=(const Base256 &rhs) { + div(rhs.data); + return *this; + } + + [[nodiscard]] friend bool operator==(const Base256 &lhs, const Base256 &rhs) { + return isEqual(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator!=(const Base256 &lhs, const Base256 &rhs) { + return !isEqual(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator>(const Base256 &lhs, const Base256 &rhs) { + return isBigger(lhs.data, rhs.data); + } + + [[nodiscard]] friend bool operator<(const Base256 &lhs, const Base256 &rhs) { + return isBigger(rhs.data, lhs.data); + } + + [[nodiscard]] friend bool operator>=(const Base256 &lhs, const Base256 &rhs) { + return !isBigger(rhs.data, lhs.data); + } - private: - std::vector data; - }; -} // namespace operations + [[nodiscard]] friend bool operator<=(const Base256 &lhs, const Base256 &rhs) { + return !isBigger(lhs.data, rhs.data); + } + + private: + std::vector data; +}; +} // namespace operations #endif From 659230954e56d2642262094e756d54151b872596 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Mon, 4 May 2026 11:01:55 +0200 Subject: [PATCH 20/42] more format --- src/key.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/key.cpp b/src/key.cpp index b6717a3..0c0e6ec 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -22,7 +22,7 @@ int key::keyExists(std::string name) { std::filesystem::path keysFolder = keysPath(); // go over every file in the KEY_FOLDER - for (const auto &entry: std::filesystem::directory_iterator(keysFolder)) { + for (const auto &entry : std::filesystem::directory_iterator(keysFolder)) { // check if the name matches if (entry.path().stem().string() == name) { // check if it's a public key @@ -141,7 +141,7 @@ std::string key::base64Encode(const std::vector &data) { std::string outString; // convert the numeric value to the corresponding char of the base64 charset - for (auto c: result) { + for (auto c : result) { outString += base64Chars[c]; } @@ -171,7 +171,7 @@ std::vector key::base64Decode(std::string data) { // check for valid char code if (number > 0b111111) { std::cerr << "trying to Decode not valid char: " << letterSegment.at(i) - << std::endl; + << std::endl; continue; } dataSegment += number << i * 6; @@ -196,18 +196,14 @@ uint8_t key::getBase64Index(char letter) { } int key::createKey(std::vector *keyPublic, std::vector *keyPrivate) { - *keyPublic = { - 23, 87, 45, 190, 12, 78, 34, 210, 56, 89, 123, 67, 90, 150, 32, 76, 54, - 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, 78, 201, - 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, 170, 53, 95, 41 - }; - *keyPrivate = { - 34, 78, 123, 56, 89, 210, 45, 190, 12, 87, 67, 150, 32, 76, 54, - 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, - 78, 201, 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, - 170, 53, 95, 102, 147, 68, 191, 44, 85, 30, 177, 59, 93, 112, 135, - 79, 202, 16, 85, 40, 221, 67, 98, 23, 87, 91, 65 - }; + *keyPublic = {23, 87, 45, 190, 12, 78, 34, 210, 56, 89, 123, 67, 90, 150, 32, 76, 54, + 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, 78, 201, + 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, 170, 53, 95, 41}; + *keyPrivate = {34, 78, 123, 56, 89, 210, 45, 190, 12, 87, 67, 150, 32, 76, 54, + 200, 11, 99, 101, 145, 67, 189, 43, 88, 29, 176, 58, 92, 111, 134, + 78, 201, 15, 84, 39, 220, 66, 97, 105, 142, 71, 185, 49, 81, 27, + 170, 53, 95, 102, 147, 68, 191, 44, 85, 30, 177, 59, 93, 112, 135, + 79, 202, 16, 85, 40, 221, 67, 98, 23, 87, 91, 65}; return 1; } From bb515c6b8770e2d96338b482ad83dcf77d28400a Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Mon, 4 May 2026 14:34:33 +0200 Subject: [PATCH 21/42] some cleanups --- src/CMakeLists.txt | 1 + src/vec/helper.h | 81 ++++++++++++++++++++++-------------------- src/vec/operations.cpp | 51 ++++++++++++-------------- src/vec/operations.h | 25 +++++++------ src/vec/types.h | 8 +++++ 5 files changed, 88 insertions(+), 78 deletions(-) create mode 100644 src/vec/types.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index add70c2..e63bf0c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,6 +31,7 @@ add_library(RSA-Core key.cpp encryption.cpp vec/operations.cpp + vec/types.h ) target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/vec/helper.h b/src/vec/helper.h index 39979e8..1763057 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -1,36 +1,36 @@ #ifndef RSA_ENCRYPTOR_HELPER_H #define RSA_ENCRYPTOR_HELPER_H +#include #include -#include +#include "types.h" -[[nodiscard]] inline std::int64_t getStartBitIndex(const std::vector &a) { - std::int64_t index = 0; - std::int64_t bitsSince1 = 0; +[[nodiscard]] inline std::int64_t getStartBitIndex(const ByteArray &a) { + // Handle the absolute zero edge-case + if (a.empty() || (a.size() == 1 && a[0] == 0)) { + return -1; + } - for (const std::uint8_t number : a) { - if (number == 0) { - bitsSince1 += 8; - continue; - } - for (std::uint64_t i = 0; i < sizeof(std::uint8_t) * 8; i++) { - if ((number & (0b1 << i)) != 0) { - index += bitsSince1 + 1; - bitsSince1 = 0; - } else { - bitsSince1++; - } + // Because of normalization, the highest bit is guaranteed + // to be in the very last byte of the vector. + const std::int64_t highestByteIndex = a.size() - 1; + const std::uint8_t highestByte = a.back(); + + // Find the highest bit in just this one byte (max 8 iterations) + for (std::int64_t bit = 7; bit >= 0; bit--) { + if ((highestByte & (0b1 << bit)) != 0) { + // Calculate total bit index: (Byte Position * 8) + Bit Position + return (highestByteIndex * 8) + bit; } } - // Returning signed int64_t stops negative values from underflowing - return index - 1; + assert(false); } -[[nodiscard]] inline std::vector convertToVector(std::uint64_t number) noexcept { - std::vector result; +[[nodiscard]] inline ByteArray convertToVector(std::uint64_t number) noexcept { + ByteArray result; - // Securely ensure 0 results in at least [0], never[] + // Ensure 0 results in at least [0], never[] if (number == 0) return {0}; while (number) { @@ -41,22 +41,23 @@ return result; } -[[nodiscard]] inline std::vector addBitFromNumber( - const std::vector &number, const std::vector &pickNumber, - const std::int64_t index) { - std::vector result; +[[nodiscard]] inline ByteArray addBitFromNumber( + const ByteArray &numberToShift, + const ByteArray &sourceNumber, + const std::int64_t bitIndex) { + ByteArray result; // Validated boundary access - if (index < 0 || index > getStartBitIndex(pickNumber)) { + if (bitIndex < 0 || bitIndex > getStartBitIndex(sourceNumber)) { return {0}; } - bool mostSignificantBit = (pickNumber[index / 8] & (0b1 << (index % 8))) != 0; + bool mostSignificantBit = (sourceNumber[bitIndex / 8] & (0b1 << (bitIndex % 8))) != 0; - for (std::uint8_t currentByte : number) { - // FIX: The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets + for (const std::uint8_t currentByte : numberToShift) { + // The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets // overwritten/shifted. - bool nextMSB = (currentByte & 0x80) != 0; + const bool nextMSB = (currentByte & 0x80) != 0; result.push_back(static_cast((currentByte << 1) | mostSignificantBit)); mostSignificantBit = nextMSB; } @@ -66,8 +67,8 @@ return result; } -[[nodiscard]] inline bool isBigger(const std::vector &a, - const std::vector &b) noexcept { +[[nodiscard]] inline bool isBigger(const ByteArray &a, + const ByteArray &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; @@ -78,23 +79,25 @@ if (aValue > bValue) { return true; - } else if (aValue < bValue) { - return false; // FIX: Ensure false is explicitly returned if the checking bit drops - // behind. + } + + // Explicitly return false if the checking bit drops behind + if (aValue < bValue) { + return false; } } return false; } -[[nodiscard]] inline bool isEqual(const std::vector &a, - const std::vector &b) noexcept { +[[nodiscard]] inline bool isEqual(const ByteArray &a, + const ByteArray &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; const std::uint32_t shortest = aSize < bSize ? aSize : bSize; - const std::vector longest = aSize > bSize ? a : b; + const ByteArray longest = aSize > bSize ? a : b; for (std::uint64_t i = 0; i < iterations; i++) { if (i >= shortest) { @@ -107,7 +110,7 @@ return true; } -[[nodiscard]] inline bool isZero(const std::vector &a) { +[[nodiscard]] inline bool isZero(const ByteArray &a) { for (const std::uint8_t number : a) { if (number != 0) return false; } diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index 5dd9a77..503551a 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -2,10 +2,10 @@ #include -void operations::Base256::add(const std::vector &b) noexcept { +void operations::Base256::add(const ByteArray &b) noexcept { // Time complexity O(iterations) // Initialize the result vector to store the sum - std::vector result; + ByteArray result; // Get the max iterations based on the largest vector const int iterations = std::max(data.size(), b.size()); @@ -43,21 +43,19 @@ void operations::Base256::add(const std::vector &b) noexcept { } // Strip mathematical leading zeros (trailing in little-endian representation) - while (result.size() > 1 && result.back() == 0) { - result.pop_back(); - } + normalizeVector(result); data = std::move(result); } -[[nodiscard]] std::vector operations::Base256::sub( - const std::vector &a, const std::vector &b) noexcept { +[[nodiscard]] ByteArray operations::Base256::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}; } - std::vector result; + ByteArray result; // Handle an underflow when subtracting bool borrow = false; @@ -88,26 +86,24 @@ void operations::Base256::add(const std::vector &b) noexcept { } // Strip trailing zeroes to normalize - while (result.size() > 1 && result.back() == 0) { - result.pop_back(); - } + normalizeVector(result); return result; } // The return value can only be positive, if it would be negative, 0 is returned -void operations::Base256::sub(const std::vector &b) noexcept { - std::vector result = sub(data, b); +void operations::Base256::sub(const ByteArray &b) noexcept { + ByteArray result = sub(data, b); data = std::move(result); } -void operations::Base256::mul(const std::vector &b) noexcept { +void operations::Base256::mul(const ByteArray &b) noexcept { // Time complexity O(aSize * bSize) const std::uint64_t aSize = data.size(); const std::uint64_t bSize = b.size(); // Initialize the result vector with zeros, with the size of aSize + bSize - std::vector result(aSize + bSize, 0); + ByteArray result(aSize + bSize, 0); for (std::uint64_t i = 0; i < aSize; i++) { // Set up the carry value for each iteration @@ -116,7 +112,7 @@ void operations::Base256::mul(const std::vector &b) noexcept { for (uint64_t x = 0; x < bSize; x++) { // Calculate the product by adding up the previous result, the carry, and the new // product - std::uint16_t product = result[i + x] + carry + (data[i] * b[x]); + const std::uint16_t product = result[i + x] + carry + (data[i] * b[x]); // Calculate the carry which is the overflow beyond 255 carry = product >> 8; @@ -131,15 +127,13 @@ void operations::Base256::mul(const std::vector &b) noexcept { } // Since aSize + bSize typically provides extra buffering, normalise the number safely - while (result.size() > 1 && result.back() == 0) { - result.pop_back(); - } + normalizeVector(result); data = std::move(result); } -void operations::Base256::div(const std::vector &divisor, - std::vector *remaining) noexcept { +void operations::Base256::div(const ByteArray &divisor, + ByteArray *remaining) noexcept { if (isZero(divisor)) { data = {0}; if (remaining != nullptr) *remaining = {0}; @@ -154,10 +148,10 @@ void operations::Base256::div(const std::vector &divisor, } // Vector preallocated to support max potential bits mapped by initial index - std::vector quotient((initialDividendIndex / 8) + 1, 0); + ByteArray quotient((initialDividendIndex / 8) + 1, 0); std::int64_t dividendIndex = initialDividendIndex; - std::vector dividendMask = addBitFromNumber({0}, data, dividendIndex--); + ByteArray dividendMask = addBitFromNumber({0}, data, dividendIndex--); while (dividendIndex >= -1) { // Evaluate mathematical power index representing current bit generated @@ -185,18 +179,17 @@ void operations::Base256::div(const std::vector &divisor, } // Strip trailing normalization zeros (empty space buffers) securely - while (quotient.size() > 1 && quotient.back() == 0) { - quotient.pop_back(); - } + normalizeVector(quotient); + if (quotient.empty()) quotient.push_back(0); data = std::move(quotient); } -[[nodiscard]] std::vector operations::Base256::pow( - const std::vector &a, const std::uint64_t &pow) noexcept { +[[nodiscard]] ByteArray operations::Base256::pow( + const ByteArray &a, const std::uint64_t &pow) noexcept { // Copy the value from an into result while keeping a constant - std::vector result; + ByteArray result; std::copy(a.begin(), a.end(), std::back_inserter(result)); // Start the loop at 1, because the first number is already assigned to result diff --git a/src/vec/operations.h b/src/vec/operations.h index c93d304..6c0bc1a 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -3,7 +3,6 @@ #include #include -#include #include "helper.h" @@ -16,19 +15,19 @@ class Base256 { Base256() { data = convertToVector(0); } - void add(const std::vector &b) noexcept; + void add(const ByteArray &b) noexcept; - void sub(const std::vector &b) noexcept; + void sub(const ByteArray &b) noexcept; - std::vector sub(const std::vector &a, - const std::vector &b) noexcept; + ByteArray sub(const ByteArray &a, + const ByteArray &b) noexcept; - void mul(const std::vector &b) noexcept; + void mul(const ByteArray &b) noexcept; - void div(const std::vector &divisor, - std::vector *remaining = nullptr) noexcept; + void div(const ByteArray &divisor, + ByteArray *remaining = nullptr) noexcept; - std::vector pow(const std::vector &a, + ByteArray pow(const ByteArray &a, const std::uint64_t &pow) noexcept; void print() const { @@ -40,6 +39,12 @@ class Base256 { std::cout << value << '\n'; } + void normalizeVector(ByteArray& a) const { + while (a.size() > 1 && a.back() == 0) { + a.pop_back(); + } + } + Base256 &operator=(const Base256 &other) { // We protect us from self assignment if (this != &other) { @@ -117,7 +122,7 @@ class Base256 { } private: - std::vector data; + ByteArray data; }; } // namespace operations diff --git a/src/vec/types.h b/src/vec/types.h new file mode 100644 index 0000000..de83b05 --- /dev/null +++ b/src/vec/types.h @@ -0,0 +1,8 @@ +#ifndef RSA_ENCRYPTOR_TYPES_H +#define RSA_ENCRYPTOR_TYPES_H + +#include + +using ByteArray = std::vector; + +#endif //RSA_ENCRYPTOR_TYPES_H From a0ad6852c8097a408be6559b17f553a36f055c5b Mon Sep 17 00:00:00 2001 From: SecondJochen <213428453+SecondJochen@users.noreply.github.com> Date: Mon, 4 May 2026 12:35:54 +0000 Subject: [PATCH 22/42] Apply Clang formatting --- src/vec/helper.h | 15 +++++++-------- src/vec/operations.cpp | 10 ++++------ src/vec/operations.h | 11 ++++------- src/vec/types.h | 2 +- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/vec/helper.h b/src/vec/helper.h index 1763057..cdb7025 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -2,7 +2,9 @@ #define RSA_ENCRYPTOR_HELPER_H #include + #include + #include "types.h" [[nodiscard]] inline std::int64_t getStartBitIndex(const ByteArray &a) { @@ -41,10 +43,9 @@ return result; } -[[nodiscard]] inline ByteArray addBitFromNumber( - const ByteArray &numberToShift, - const ByteArray &sourceNumber, - const std::int64_t bitIndex) { +[[nodiscard]] inline ByteArray addBitFromNumber(const ByteArray &numberToShift, + const ByteArray &sourceNumber, + const std::int64_t bitIndex) { ByteArray result; // Validated boundary access @@ -67,8 +68,7 @@ return result; } -[[nodiscard]] inline bool isBigger(const ByteArray &a, - const ByteArray &b) noexcept { +[[nodiscard]] inline bool isBigger(const ByteArray &a, const ByteArray &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; @@ -90,8 +90,7 @@ return false; } -[[nodiscard]] inline bool isEqual(const ByteArray &a, - const ByteArray &b) noexcept { +[[nodiscard]] inline bool isEqual(const ByteArray &a, const ByteArray &b) noexcept { const std::uint32_t aSize = a.size(); const std::uint32_t bSize = b.size(); const std::uint64_t iterations = aSize > bSize ? aSize : bSize; diff --git a/src/vec/operations.cpp b/src/vec/operations.cpp index 503551a..cde792c 100644 --- a/src/vec/operations.cpp +++ b/src/vec/operations.cpp @@ -48,8 +48,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::Base256::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}; @@ -132,8 +131,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::Base256::div(const ByteArray &divisor, ByteArray *remaining) noexcept { if (isZero(divisor)) { data = {0}; if (remaining != nullptr) *remaining = {0}; @@ -186,8 +184,8 @@ void operations::Base256::div(const ByteArray &divisor, data = std::move(quotient); } -[[nodiscard]] ByteArray operations::Base256::pow( - const ByteArray &a, const std::uint64_t &pow) noexcept { +[[nodiscard]] ByteArray operations::Base256::pow(const ByteArray &a, + const std::uint64_t &pow) noexcept { // Copy the value from an into result while keeping a constant ByteArray result; std::copy(a.begin(), a.end(), std::back_inserter(result)); diff --git a/src/vec/operations.h b/src/vec/operations.h index 6c0bc1a..2c50bce 100644 --- a/src/vec/operations.h +++ b/src/vec/operations.h @@ -19,16 +19,13 @@ class Base256 { void sub(const ByteArray &b) noexcept; - ByteArray sub(const ByteArray &a, - const ByteArray &b) noexcept; + ByteArray sub(const ByteArray &a, const ByteArray &b) noexcept; void mul(const ByteArray &b) noexcept; - void div(const ByteArray &divisor, - ByteArray *remaining = nullptr) noexcept; + void div(const ByteArray &divisor, ByteArray *remaining = nullptr) noexcept; - ByteArray pow(const ByteArray &a, - const std::uint64_t &pow) noexcept; + ByteArray pow(const ByteArray &a, const std::uint64_t &pow) noexcept; void print() const { std::uint64_t value = 0; @@ -39,7 +36,7 @@ class Base256 { std::cout << value << '\n'; } - void normalizeVector(ByteArray& a) const { + void normalizeVector(ByteArray &a) const { while (a.size() > 1 && a.back() == 0) { a.pop_back(); } diff --git a/src/vec/types.h b/src/vec/types.h index de83b05..24686ef 100644 --- a/src/vec/types.h +++ b/src/vec/types.h @@ -5,4 +5,4 @@ using ByteArray = std::vector; -#endif //RSA_ENCRYPTOR_TYPES_H +#endif // RSA_ENCRYPTOR_TYPES_H From 09abf3acb55a02f2d3e50c7ef5c0e0172ba8c7a5 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 09:56:05 +0200 Subject: [PATCH 23/42] more cleanup & update docs --- src/docs/add.md | 8 ++++++++ src/docs/base256.md | 11 +++++------ src/docs/sub.md | 7 +++++++ src/main.cpp | 14 -------------- src/vec/helper.h | 8 +++++++- 5 files changed, 27 insertions(+), 21 deletions(-) create mode 100644 src/docs/add.md create mode 100644 src/docs/sub.md diff --git a/src/docs/add.md b/src/docs/add.md new file mode 100644 index 0000000..c5ede52 --- /dev/null +++ b/src/docs/add.md @@ -0,0 +1,8 @@ +# Addition Logic +The addition algorithm uses a `uint16_t` accumulator to detect overflows beyond the 8-bit boundary (255). + +1. **Iteration:** The algorithm traverses from the LSB (`index 0`) to the MSB (`index N`). +2. **Carry Propagation:** At each position `i`, it calculates: + `sum = a[i] + b[i] + carry` +3. **Overflow Handling:** The carry for the next iteration is determined by `sum >> 8`. The digit stored in the current position is determined by `sum & 0xFF`. +4. **Finalization:** If a carry remains after the final byte is processed, an additional byte is pushed to the vector, extending the total magnitude of the number. \ No newline at end of file diff --git a/src/docs/base256.md b/src/docs/base256.md index 840a2e5..0ee6eb6 100644 --- a/src/docs/base256.md +++ b/src/docs/base256.md @@ -17,6 +17,7 @@ Each digit holds a value from `0` to `255`. If an arithmetic operation pushes a ### 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. @@ -35,15 +36,13 @@ The class enforces strict **Array Normalization**. After every operation, traili ## Algorithmic Mechanics -### Addition & Subtraction (`O(N)`) -These operations execute in linear time, sweeping from index `0` upwards. -* **Addition:** Evaluates `a[i] + b[i] + carry` using a `uint16_t` buffer. If the sum exceeds `0xFF` (255), the bitwise shift `sum >> 8` extracts the carry, and the modulo `sum & 0xFF` is pushed to the result. -* **Subtraction:** Subtracts byte-by-byte. If `a[i] < b[i]`, a `borrow` is triggered, adding `256` to the current byte and subtracting `1` from the subsequent index. Because the class represents unsigned rings, any subtraction resulting in a global negative state is actively intercepted and clamped strictly to `0`. +- [Addition](add.md#addition-logic) +- [Subtraction](sub.md#subtraction-logic) -### Multiplication (`O(N * M)`) +### Multiplication Multiplication implements a double-loop accumulator matrix. It guarantees no reallocation overhead by pre-allocating a vector of size `A.size() + B.size()`—the maximum possible magnitude of a product. It iterates through every byte of `A`, multiplies it by every byte of `B`, and seamlessly propagates 16-bit carries up the pre-allocated vector indices. -### Division (`O(Bits)`) +### Division To avoid the disastrous performance of iterative subtraction, division utilizes highly optimized **Bitwise Long Division**. It calculates the exact highest active bit (`getStartBitIndex`), shifting a scoped `dividendMask` over the divisor one bit at a time. If the mask is larger than the divisor, the divisor is subtracted and the corresponding bit in the newly constructed `quotient` vector is toggled to `1`. diff --git a/src/docs/sub.md b/src/docs/sub.md new file mode 100644 index 0000000..6fd7008 --- /dev/null +++ b/src/docs/sub.md @@ -0,0 +1,7 @@ +# Subtraction Logic +Subtraction implements a "borrow" propagation system to ensure mathematical correctness within an unsigned integer space: + +1. **Underflow Protection:** Before processing, `isBigger` is invoked. If `b > a`, the result is clamped to `0` because negative numbers are not handled. +2. **Borrow Propagation:** If `a[i] < b[i]`, the operation sets a `borrow` flag. +3. **Regrouping:** The algorithm effectively borrows `256` from the next magnitude (`index i + 1`), performing `a[i] + 256 - b[i]`. +4. **Normalization:** Post-calculation, `normalizeVector` is called to prune leading zeros, ensuring the vector size remains proportional to the actual magnitude. \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0723b35..f393ac7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,8 +6,6 @@ #include "cli.h" #include "key.h" -#include "utility.h" -#include "vec/operations.h" int main(int argc, char *argv[]) { struct { @@ -15,18 +13,6 @@ int main(int argc, char *argv[]) { bool publicKey = false; } arguments; - using operations::Base256; - - Base256 data1(100); - Base256 data2(400); - - data2.print(); - Base256 result = 200; - - result = data1 + data2; - - std::cout << (result >= Base256(500)) << std::endl; - // Check if there are additional arguments if (argc > 1) { // Check for additional arguments diff --git a/src/vec/helper.h b/src/vec/helper.h index cdb7025..f4b80a7 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -53,7 +53,13 @@ return {0}; } - bool mostSignificantBit = (sourceNumber[bitIndex / 8] & (0b1 << (bitIndex % 8))) != 0; + // bitIndex & 7 is equivalent to bitIndex % 8 + const int sourceNumberMask = 0b1 << (bitIndex & 7); + + // bitIndex >> 3 is equivalent to bitIndex / 8 + const int sourceNumberIndex = bitIndex >> 3; + + bool mostSignificantBit = (sourceNumber[sourceNumberIndex] & sourceNumberMask) != 0; for (const std::uint8_t currentByte : numberToShift) { // The mask for MSB is 0x80 (128). We evaluate this BEFORE currentByte gets From a78db1af2615690ec35b9dfba18038b5b1510512 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 10:03:22 +0200 Subject: [PATCH 24/42] rename file name to match class name --- src/CMakeLists.txt | 2 +- src/tests/test_base256.cpp | 2 +- src/vec/{operations.cpp => base256.cpp} | 2 +- src/vec/{operations.h => base256.h} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename src/vec/{operations.cpp => base256.cpp} (99%) rename src/vec/{operations.h => base256.h} (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e63bf0c..33bdaaa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,7 +30,7 @@ add_library(RSA-Core utility.cpp key.cpp encryption.cpp - vec/operations.cpp + vec/base256.cpp vec/types.h ) target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/tests/test_base256.cpp b/src/tests/test_base256.cpp index 9613d5e..d42b7d1 100644 --- a/src/tests/test_base256.cpp +++ b/src/tests/test_base256.cpp @@ -3,7 +3,7 @@ #include #include -#include "vec/operations.h" +#include "vec/base256.h" using operations::Base256; diff --git a/src/vec/operations.cpp b/src/vec/base256.cpp similarity index 99% rename from src/vec/operations.cpp rename to src/vec/base256.cpp index cde792c..ebf0c98 100644 --- a/src/vec/operations.cpp +++ b/src/vec/base256.cpp @@ -1,4 +1,4 @@ -#include "operations.h" +#include "base256.h" #include diff --git a/src/vec/operations.h b/src/vec/base256.h similarity index 100% rename from src/vec/operations.h rename to src/vec/base256.h From 2d8298358bdffa0e20a1e417ae72b46b1c190a3b Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 10:40:48 +0200 Subject: [PATCH 25/42] move docs and test to root --- {src/docs => docs}/add.md | 0 {src/docs => docs}/base256.md | 0 {src/docs => docs}/sub.md | 0 src/CMakeLists.txt | 2 +- {src/tests => tests}/test_base256.cpp | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename {src/docs => docs}/add.md (100%) rename {src/docs => docs}/base256.md (100%) rename {src/docs => docs}/sub.md (100%) rename {src/tests => tests}/test_base256.cpp (100%) diff --git a/src/docs/add.md b/docs/add.md similarity index 100% rename from src/docs/add.md rename to docs/add.md diff --git a/src/docs/base256.md b/docs/base256.md similarity index 100% rename from src/docs/base256.md rename to docs/base256.md diff --git a/src/docs/sub.md b/docs/sub.md similarity index 100% rename from src/docs/sub.md rename to docs/sub.md diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 33bdaaa..95f1354 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,7 +42,7 @@ target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) # Tests if (BUILD_TESTING) add_executable(RSA-Encryptor-Tests - tests/test_base256.cpp + ../tests/test_base256.cpp ) target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(RSA-Encryptor-Tests diff --git a/src/tests/test_base256.cpp b/tests/test_base256.cpp similarity index 100% rename from src/tests/test_base256.cpp rename to tests/test_base256.cpp From ca182e6d5c8b54be8804e599ebf9f35c34de9152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= <134922046+LordofGhost@users.noreply.github.com> Date: Tue, 5 May 2026 11:01:39 +0200 Subject: [PATCH 26/42] Add base256 library to CMake configuration in separate file --- src/CMakeLists.txt | 2 -- src/vec/CMakeLists.txt | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/vec/CMakeLists.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95f1354..32511fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -30,8 +30,6 @@ add_library(RSA-Core utility.cpp key.cpp encryption.cpp - vec/base256.cpp - vec/types.h ) target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/vec/CMakeLists.txt b/src/vec/CMakeLists.txt new file mode 100644 index 0000000..f1eb257 --- /dev/null +++ b/src/vec/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library( + base256.cpp +) \ No newline at end of file From bbe534ef3038cbf788805564ea55273748ef5970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= <134922046+LordofGhost@users.noreply.github.com> Date: Tue, 5 May 2026 11:15:14 +0200 Subject: [PATCH 27/42] Refactor CMake configuration and update file paths for core components --- CMakeLists.txt | 6 +++- src/CMakeLists.txt | 53 ++--------------------------------- src/cli.cpp | 2 +- src/core/CMakeLists.txt | 7 +++++ src/{ => core}/encryption.cpp | 0 src/{ => core}/encryption.h | 0 src/{ => core}/key.cpp | 0 src/{ => core}/key.h | 0 src/{ => core}/utility.cpp | 0 src/{ => core}/utility.h | 0 src/main.cpp | 2 +- src/vec/CMakeLists.txt | 2 +- tests/CMakeLists.txt | 37 ++++++++++++++++++++++++ 13 files changed, 54 insertions(+), 55 deletions(-) create mode 100644 src/core/CMakeLists.txt rename src/{ => core}/encryption.cpp (100%) rename src/{ => core}/encryption.h (100%) rename src/{ => core}/key.cpp (100%) rename src/{ => core}/key.h (100%) rename src/{ => core}/utility.cpp (100%) rename src/{ => core}/utility.h (100%) create mode 100644 tests/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index cb29322..97fbf45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ cmake_minimum_required(VERSION 3.28.3) project(RSA-Encryptor) +# Enforce C++20 and disable compiler-specific extensions set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -add_subdirectory(src) \ No newline at end of file +add_subdirectory(src) +add_subdirectory(tests) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32511fa..1572ac9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,56 +1,7 @@ cmake_minimum_required(VERSION 3.20) project(RSA-Encryptor CXX) -# Enforce C++20 and disable compiler-specific extensions -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -# Prevent CMake from re-downloading Catch2 every time CLion reloads -set(FETCHCONTENT_UPDATES_DISCONNECTED ON CACHE BOOL "Don't update FetchContent on every config" FORCE) - -# include(CTest) automatically sets BUILD_TESTING to ON by default -include(CTest) - -# Catch2 via FetchContent -include(FetchContent) -FetchContent_Declare( - Catch2 - GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.11.0 -) -FetchContent_MakeAvailable(Catch2) - -# Find Catch 2 CMakeList -list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) -include(Catch) - -# Core library -add_library(RSA-Core - utility.cpp - key.cpp - encryption.cpp -) -target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -# RSA-Encryptor Main add_executable(RSA-Encryptor main.cpp cli.cpp) -target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) - -# Tests -if (BUILD_TESTING) - add_executable(RSA-Encryptor-Tests - ../tests/test_base256.cpp - ) - target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(RSA-Encryptor-Tests - PRIVATE - RSA-Core - Catch2::Catch2WithMain - ) - catch_discover_tests(RSA-Encryptor-Tests - TEST_PREFIX base256: - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) -endif () \ No newline at end of file +add_subdirectory(core) +add_subdirectory(vec) \ No newline at end of file diff --git a/src/cli.cpp b/src/cli.cpp index bcae579..0fd1f51 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -2,7 +2,7 @@ #include -#include "key.h" +#include "core/key.h" void cli::help() { std::cout << "Welcome to RSA-Encryptor" << std::endl; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..a9c5c95 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,7 @@ +add_library(RSA-Core + utility.cpp + key.cpp + encryption.cpp +) +target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) diff --git a/src/encryption.cpp b/src/core/encryption.cpp similarity index 100% rename from src/encryption.cpp rename to src/core/encryption.cpp diff --git a/src/encryption.h b/src/core/encryption.h similarity index 100% rename from src/encryption.h rename to src/core/encryption.h diff --git a/src/key.cpp b/src/core/key.cpp similarity index 100% rename from src/key.cpp rename to src/core/key.cpp diff --git a/src/key.h b/src/core/key.h similarity index 100% rename from src/key.h rename to src/core/key.h diff --git a/src/utility.cpp b/src/core/utility.cpp similarity index 100% rename from src/utility.cpp rename to src/core/utility.cpp diff --git a/src/utility.h b/src/core/utility.h similarity index 100% rename from src/utility.h rename to src/core/utility.h diff --git a/src/main.cpp b/src/main.cpp index f393ac7..8d4c308 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,7 @@ #include #include "cli.h" -#include "key.h" +#include "core/key.h" int main(int argc, char *argv[]) { struct { diff --git a/src/vec/CMakeLists.txt b/src/vec/CMakeLists.txt index f1eb257..06d70c1 100644 --- a/src/vec/CMakeLists.txt +++ b/src/vec/CMakeLists.txt @@ -1,3 +1,3 @@ -add_library( +add_library(RSA-Vec base256.cpp ) \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..c86ce55 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,37 @@ + +# Prevent CMake from re-downloading Catch2 every time CLion reloads +set(FETCHCONTENT_UPDATES_DISCONNECTED ON CACHE BOOL "Don't update FetchContent on every config" FORCE) + +# include(CTest) automatically sets BUILD_TESTING to ON by default +include(CTest) + +# Catch2 via FetchContent +include(FetchContent) +FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.11.0 +) +FetchContent_MakeAvailable(Catch2) + +# Find Catch 2 CMakeList +list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) +include(Catch) + + +if (BUILD_TESTING) + add_executable(RSA-Encryptor-Tests + ../tests/test_base256.cpp + ) + target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(RSA-Encryptor-Tests + PRIVATE + RSA-Core + Catch2::Catch2WithMain + ) + + catch_discover_tests(RSA-Encryptor-Tests + TEST_PREFIX base256: + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endif () From d122f7061a67ec5cb99f6f0fb71663875c504b6e Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 11:39:36 +0200 Subject: [PATCH 28/42] expand docummentation using gemini 3.1 pro --- docs/add.md | 2 +- docs/base256.md | 14 ++-- docs/div.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/mul.md | 35 ++++++++++ docs/sub.md | 2 +- 5 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 docs/div.md create mode 100644 docs/mul.md diff --git a/docs/add.md b/docs/add.md index c5ede52..6a8849b 100644 --- a/docs/add.md +++ b/docs/add.md @@ -1,4 +1,4 @@ -# Addition Logic +# Addition The addition algorithm uses a `uint16_t` accumulator to detect overflows beyond the 8-bit boundary (255). 1. **Iteration:** The algorithm traverses from the LSB (`index 0`) to the MSB (`index N`). diff --git a/docs/base256.md b/docs/base256.md index 0ee6eb6..98e918c 100644 --- a/docs/base256.md +++ b/docs/base256.md @@ -36,16 +36,10 @@ The class enforces strict **Array Normalization**. After every operation, traili ## Algorithmic Mechanics -- [Addition](add.md#addition-logic) -- [Subtraction](sub.md#subtraction-logic) - -### Multiplication -Multiplication implements a double-loop accumulator matrix. It guarantees no reallocation overhead by pre-allocating a vector of size `A.size() + B.size()`—the maximum possible magnitude of a product. It iterates through every byte of `A`, multiplies it by every byte of `B`, and seamlessly propagates 16-bit carries up the pre-allocated vector indices. - -### Division -To avoid the disastrous performance of iterative subtraction, division utilizes highly optimized **Bitwise Long Division**. -It calculates the exact highest active bit (`getStartBitIndex`), shifting a scoped `dividendMask` over the divisor one bit at a time. If the mask is larger than the divisor, the divisor is subtracted and the corresponding bit in the newly constructed `quotient` vector is toggled to `1`. - +- [Addition](add.md#addition) +- [Subtraction](sub.md#subtraction) +- [Subtraction](mul.md#multiplication) +- [Subtraction](div.md#division) --- ## Code Examples & Usage diff --git a/docs/div.md b/docs/div.md new file mode 100644 index 0000000..912c5e1 --- /dev/null +++ b/docs/div.md @@ -0,0 +1,166 @@ +# Division + +Because iterative subtraction (e.g., subtracting `3` from `1,000,000` one by one) is computationally disastrous for Big-Integers, the `Base256` class implements **Bitwise Long Division** (Binary Long Division). + +This algorithm evaluates the quotient one **bit** at a time, moving from the Most Significant Bit (MSB) down to the Least Significant Bit (LSB). + +## Definitions +To understand the division algorithm, it is important to define the mathematical terms used in the context of the code: +* **Dividend (`data`):** The number being divided (the total amount you start with). +* **Divisor (`divisor`):** The number you are dividing by (how large each "group" is). +* **Quotient (`quotient`):** The primary answer. How many times the divisor fits entirely into the dividend. +* **Remainder / Modulo (`remaining`):** The leftover amount that is strictly less than the divisor. +* **Dividend Mask (`dividendMask`):** The active "working remainder" used dynamically within the long division loop to evaluate the current subset of bits. + +## The Basic Concept +Binary Long Division works exactly like the long division taught in elementary school, but it is vastly simpler because the quotient at any step can only ever be `1` or `0` (it either fits, or it doesn't). +1. Isolate the highest piece of the dividend. +2. Check if the divisor fits into it. +3. If it fits, write `1` to the quotient, subtract the divisor, and "bring down" the next bit. +4. If it doesn't fit, write `0` to the quotient, don't subtract, and "bring down" the next bit. + +**Quick Binary Example (`7 / 2`):** +To see this in action, let's divide `111` (7 in decimal) by `10` (2 in decimal). + +**The Setup:** +```text + [Quotient] + _____________ +Divisor: 10 | 1 1 1 <-- Dividend + ^ ^ + MSB LSB + (Highest) (Lowest) +``` + +**Step 1: Isolate the highest bit (MSB)** +```text + 0 <-- 10 does not fit into 1. Write 0 to quotient. + _______ + 10 | 1 1 1 + - 0 <-- No subtraction occurs. + --- + 1 <-- Working remainder is 1. +``` + +**Step 2: Bring down the next bit** +```text + 0 1 <-- 10 fits into 11! Write 1 to quotient. + _______ + 10 | 1 1 1 + . | + 1 1 <-- Bring down the 1. Working remainder is now 11. + - 1 0 <-- Subtract divisor (10). + ----- + 1 <-- New working remainder is 1. +``` + +**Step 3: Bring down the last bit (LSB)** +```text + 0 1 1 <-- 10 fits into 11! Write 1 to quotient. + _______ + 10 | 1 1 1 + . | + 1 1 <-- Bring down the final 1. Working remainder is 11. + - 1 0 <-- Subtract divisor (10). + ----- + 1 <-- Final Remainder! +``` + +**Result:** +Reading the top from left to right, our built quotient is **`011`** (which evaluates to `3` in decimal). The leftover value at the bottom is our remainder: **`1`**. +*(7 / 2 = 3 R 1)*. + +## 1. Edge Cases and Initialization +Before any loops begin, the algorithm secures the mathematical boundaries: +1. **Division by Zero:** If the divisor is `0`, the operation safely aborts, defaulting the quotient and remainder to `0`. +2. **Finding the MSB (`getStartBitIndex`):** The algorithm scans the dividend to find the exact global index of the highest `1` bit (`initialDividendIndex`). This prevents the algorithm from processing leading mathematical "ghost zeros." +3. **Preallocation:** Because the exact bit-length of the quotient correlates directly to the `initialDividendIndex`, the `quotient` vector is pre-allocated to its exact maximum required size (`(initialDividendIndex / 8) + 1`). This completely eliminates memory reallocation overhead during the division loop. + +## 2. The Working Remainder (`dividendMask`) +The algorithm initializes the `dividendMask` by taking a value of `0`, shifting it left by 1, and "bringing down" the absolute highest bit from the dividend. + +## 3. The Bitwise Evaluation Loop +The algorithm enters a `while` loop, processing bit by bit from `initialDividendIndex` down to `-1`. +For every bit position, the mathematical power index (`currentQBitIndex`) is evaluated. + +At each step, a Lexicographical Comparison (`isBigger` / `isEqual`) checks if the `dividendMask` (working remainder) is $\ge$ the `divisor`. + +**Case A: The Mask is $\ge$ the Divisor** +* The divisor "fits" inside the working remainder. +* A `1` is written to the exact corresponding bit inside the pre-allocated `quotient` vector using a bitwise OR: `quotient[byteIndex] |= (1 << bitIndex)`. +* The `divisor` is subtracted from the `dividendMask`. +* If there are still bits left in the dividend, the `dividendMask` is shifted left by 1, and the *next* bit from the dividend is brought down into the LSB of the mask. + +**Case B: The Mask is $<$ the Divisor** +* The divisor does not fit. The quotient bit remains `0` (its default pre-allocated state). +* No subtraction occurs. +* The `dividendMask` is simply shifted left by 1, and the *next* bit from the dividend is brought down to increase the value of the mask for the next loop iteration. + +## 4. Modulo (The Remainder) +Because this is integer division, there is often a fractional remainder. The `div` function accepts an optional pointer to a `remaining` vector (`Bytes* remaining`). +When the loop finishes processing the final bit (`dividendIndex < 0`), whatever mathematical value is left inside the `dividendMask` is exactly the modulo. If the pointer is provided, the mask is copied into it. + +*(Note: Because of this architecture, evaluating `A % B` requires the exact same computational effort as `A / B`. Therefore, if both the quotient and remainder are needed, they are extracted simultaneously to halve CPU cycles).* + +## 5. Final Normalization +Even though pre-allocation is tightly bound to the `initialDividendIndex`, the final quotient might have leading zeros depending on the magnitude of the divisor. The `div` function concludes by stripping any trailing zero-bytes from the little-endian vector to maintain strict `Base256` normalization guarantees. + +--- + +## Visualization: `13 / 3` (Binary Long Division) +To understand how the bitwise loop processes numbers, let's trace the division of `13` by `3`. +* **Dividend:** `13` $\rightarrow$ Binary: `1101` +* **Divisor:** `3` $\rightarrow$ Binary: `0011` + +**Initialization:** +* `getStartBitIndex(1101)` returns `3` (the 0-based index of the highest `1` bit). +* `dividendMask` (the working remainder) is initialized to `0000`. +* `quotient` is pre-allocated and initialized to `0000`. + +### Step 1: Processing Bit Index 3 +* **Action:** Shift `dividendMask` left by 1, and bring down Bit 3 of the dividend (`1`). +* **Mask Status:** `dividendMask` becomes `0001` (Decimal: 1). +* **Comparison:** Is `0001` $\ge$ `0011` (Divisor)? $\rightarrow$ **FALSE** +* **Result:** + * Divisor does not fit. No subtraction. + * Bit 3 of `quotient` remains `0`. + * **Current Quotient:** `0000` + * **Current Mask:** `0001` + +### Step 2: Processing Bit Index 2 +* **Action:** Shift `dividendMask` left by 1 (`0001` $\rightarrow$ `0010`), and bring down Bit 2 of the dividend (`1`). +* **Mask Status:** `dividendMask` becomes `0011` (Decimal: 3). +* **Comparison:** Is `0011` $\ge$ `0011` (Divisor)? $\rightarrow$ **TRUE** +* **Result:** + * Divisor fits! + * Set Bit 2 of `quotient` to `1` using bitwise OR. + * Subtract divisor from mask: `0011` - `0011` = `0000`. + * **Current Quotient:** `0100` + * **Current Mask:** `0000` + +### Step 3: Processing Bit Index 1 +* **Action:** Shift `dividendMask` left by 1 (`0000` $\rightarrow$ `0000`), and bring down Bit 1 of the dividend (`0`). +* **Mask Status:** `dividendMask` becomes `0000` (Decimal: 0). +* **Comparison:** Is `0000` $\ge$ `0011` (Divisor)? $\rightarrow$ **FALSE** +* **Result:** + * Divisor does not fit. No subtraction. + * Bit 1 of `quotient` remains `0`. + * **Current Quotient:** `0100` + * **Current Mask:** `0000` + +### Step 4: Processing Bit Index 0 (LSB) +* **Action:** Shift `dividendMask` left by 1 (`0000` $\rightarrow$ `0000`), and bring down Bit 0 of the dividend (`1`). +* **Mask Status:** `dividendMask` becomes `0001` (Decimal: 1). +* **Comparison:** Is `0001` $\ge$ `0011` (Divisor)? $\rightarrow$ **FALSE** +* **Result:** + * Divisor does not fit. No subtraction. + * Bit 0 of `quotient` remains `0`. + * **Current Quotient:** `0100` + * **Current Mask:** `0001` + +### Final Output Evaluation: +The loop terminates because we have processed bit index `0`. +1. **The Quotient:** The pre-allocated quotient vector holds `0100`, which mathematically evaluates to **`4`**. +2. **The Remainder:** The `dividendMask` is left holding `0001`, which mathematically evaluates to **`1`**. If a pointer for the modulo was provided, this value is safely copied over. + +**Conclusion:** `1101 / 0011` = `0100` with a remainder of `0001` ($13 / 3 = 4\text{ R }1$). diff --git a/docs/mul.md b/docs/mul.md new file mode 100644 index 0000000..5abc6e9 --- /dev/null +++ b/docs/mul.md @@ -0,0 +1,35 @@ +# Multiplication + +The multiplication algorithm is a software-level implementation of **Grade-School Long Multiplication**, operating in Base-256 rather than Base-10. It systematically multiplies every byte of the first number by every byte of the second number, accumulating the sums and propagating carries. + +## 1. Pre-allocation and Space Complexity +Mathematically, multiplying a number of length `N` by a number of length `M` will result in a product with a maximum length of `N + M`. +The algorithm leverages this mathematical guarantee by pre-allocating a `result` vector initialized entirely with zeros: `std::vector result(aSize + bSize, 0)`. +This completely eliminates dynamic memory reallocations during the intensive double-loop matrix computation. + +## 2. The Accumulator Matrix (Double Loop) +The algorithm iterates through every Base-256 digit (byte) of the multiplicand `A` (index `i`), and multiplies it by every byte of the multiplier `B` (index `x`). + +Because the algorithm evaluates `A[i] * B[x]`, the resulting product mathematically belongs at the magnitude position `i + x`. The code reflects this directly by accumulating into `result[i + x]`. + +## 3. The `uint16_t` Buffer Property +One of the most elegant aspects of Base-256 arithmetic is how perfectly it maps to standard hardware integer types. Inside the inner loop, the operation calculates: +`product = result[i + x] + carry + (A[i] * B[x])` + +What is the absolute maximum value this expression can evaluate to? +* `A[i]` max is `255` +* `B[x]` max is `255` +* `carry` max is `255` +* Previous `result[i + x]` max is `255` +* Maximum calculation: $255 \times 255 + 255 + 255 = \mathbf{65535}$. + +`65535` is exactly `0xFFFF`, which is the absolute maximum value of an unsigned 16-bit integer. By casting the accumulator to `uint16_t`, the algorithm perfectly encapsulates the multiplication without risking a single bit of overflow. + +## 4. Carry Propagation +After calculating the 16-bit product: +* **The Current Byte:** `product & 0xFF` extracts the bottom 8 bits, which are stored in `result[i + x]`. +* **The Carry:** `product >> 8` extracts the top 8 bits, which become the `carry` for the next inner-loop iteration. +* **Row Carry:** When the inner loop finishes processing all bytes of `B`, any remaining `carry` is deposited at the end of the current matrix row: `result[i + bSize]`. + +## 5. Normalization +Because `N + M` represents the *maximum* possible length, the result might have one trailing `0` byte (e.g., $9 \times 9 = 81$, 1 digit $\times$ 1 digit $=$ 2 digits. But $2 \times 3 = 6$, 1 digit $\times$ 1 digit $=$ 1 digit). The `normalizeVector` function cleanly strips this excess buffer byte if it is unused. \ No newline at end of file diff --git a/docs/sub.md b/docs/sub.md index 6fd7008..7ea133b 100644 --- a/docs/sub.md +++ b/docs/sub.md @@ -1,4 +1,4 @@ -# Subtraction Logic +# Subtraction Subtraction implements a "borrow" propagation system to ensure mathematical correctness within an unsigned integer space: 1. **Underflow Protection:** Before processing, `isBigger` is invoked. If `b > a`, the result is clamped to `0` because negative numbers are not handled. From ce64d20ca9863cd10e5ddd2cd89825062ce2e4a5 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 12:00:08 +0200 Subject: [PATCH 29/42] first cleanup --- docs/div.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/div.md b/docs/div.md index 912c5e1..0c3b43e 100644 --- a/docs/div.md +++ b/docs/div.md @@ -1,26 +1,23 @@ # Division - -Because iterative subtraction (e.g., subtracting `3` from `1,000,000` one by one) is computationally disastrous for Big-Integers, the `Base256` class implements **Bitwise Long Division** (Binary Long Division). - -This algorithm evaluates the quotient one **bit** at a time, moving from the Most Significant Bit (MSB) down to the Least Significant Bit (LSB). +This documentation explains how the **Bitwise Long Division** (Binary Long Division) is implemented. ## Definitions -To understand the division algorithm, it is important to define the mathematical terms used in the context of the code: -* **Dividend (`data`):** The number being divided (the total amount you start with). -* **Divisor (`divisor`):** The number you are dividing by (how large each "group" is). -* **Quotient (`quotient`):** The primary answer. How many times the divisor fits entirely into the dividend. +* **Dividend (`data`):** The number being divided +* **Divisor (`divisor`):** The number you are dividing by +* **Quotient (`quotient`):** This is the result number. How many times the divisor fits entirely into the dividend. * **Remainder / Modulo (`remaining`):** The leftover amount that is strictly less than the divisor. * **Dividend Mask (`dividendMask`):** The active "working remainder" used dynamically within the long division loop to evaluate the current subset of bits. ## The Basic Concept -Binary Long Division works exactly like the long division taught in elementary school, but it is vastly simpler because the quotient at any step can only ever be `1` or `0` (it either fits, or it doesn't). +Binary Long Division works exactly like the long division taught in elementary school, but it is simpler because the quotient at any step can only ever be `1` or `0` (it either fits, or it doesn't). 1. Isolate the highest piece of the dividend. 2. Check if the divisor fits into it. 3. If it fits, write `1` to the quotient, subtract the divisor, and "bring down" the next bit. 4. If it doesn't fit, write `0` to the quotient, don't subtract, and "bring down" the next bit. -**Quick Binary Example (`7 / 2`):** -To see this in action, let's divide `111` (7 in decimal) by `10` (2 in decimal). +**Quick Binary Example `7 / 2`:** +- `111` (7 in decimal) +- `10` (2 in decimal) **The Setup:** ```text @@ -67,14 +64,13 @@ Divisor: 10 | 1 1 1 <-- Dividend ``` **Result:** -Reading the top from left to right, our built quotient is **`011`** (which evaluates to `3` in decimal). The leftover value at the bottom is our remainder: **`1`**. +Reading the top from left to right, our built quotient is **`011`** (`3` in decimal). The leftover value at the bottom is our remainder: **`1`**. *(7 / 2 = 3 R 1)*. ## 1. Edge Cases and Initialization -Before any loops begin, the algorithm secures the mathematical boundaries: 1. **Division by Zero:** If the divisor is `0`, the operation safely aborts, defaulting the quotient and remainder to `0`. 2. **Finding the MSB (`getStartBitIndex`):** The algorithm scans the dividend to find the exact global index of the highest `1` bit (`initialDividendIndex`). This prevents the algorithm from processing leading mathematical "ghost zeros." -3. **Preallocation:** Because the exact bit-length of the quotient correlates directly to the `initialDividendIndex`, the `quotient` vector is pre-allocated to its exact maximum required size (`(initialDividendIndex / 8) + 1`). This completely eliminates memory reallocation overhead during the division loop. +3. **Preallocation:** Because the exact bit-length of the quotient correlates directly to the `initialDividendIndex`, the `quotient` vector is pre-allocated to its exact maximum required size (`(initialDividendIndex / 8) + 1`). ## 2. The Working Remainder (`dividendMask`) The algorithm initializes the `dividendMask` by taking a value of `0`, shifting it left by 1, and "bringing down" the absolute highest bit from the dividend. From 652c612fda60fec52d59a86184caef7810f3edde Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Tue, 5 May 2026 12:40:32 +0200 Subject: [PATCH 30/42] save work --- docs/div.md | 2 +- tests/CMakeLists.txt | 1 + tests/test_base256.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/div.md b/docs/div.md index 0c3b43e..f909d45 100644 --- a/docs/div.md +++ b/docs/div.md @@ -79,7 +79,7 @@ The algorithm initializes the `dividendMask` by taking a value of `0`, shifting The algorithm enters a `while` loop, processing bit by bit from `initialDividendIndex` down to `-1`. For every bit position, the mathematical power index (`currentQBitIndex`) is evaluated. -At each step, a Lexicographical Comparison (`isBigger` / `isEqual`) checks if the `dividendMask` (working remainder) is $\ge$ the `divisor`. +At each step we check if the `dividendMask` (working remainder) is $\ge$ the `divisor`. **Case A: The Mask is $\ge$ the Divisor** * The divisor "fits" inside the working remainder. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c86ce55..306a703 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,7 @@ if (BUILD_TESTING) target_link_libraries(RSA-Encryptor-Tests PRIVATE RSA-Core + RSA-Vec Catch2::Catch2WithMain ) diff --git a/tests/test_base256.cpp b/tests/test_base256.cpp index d42b7d1..322a404 100644 --- a/tests/test_base256.cpp +++ b/tests/test_base256.cpp @@ -3,7 +3,7 @@ #include #include -#include "vec/base256.h" +#include "../src/vec/base256.h" using operations::Base256; From 2d9d854fd6c299f8e4ee4910bdd4d93965ece7c1 Mon Sep 17 00:00:00 2001 From: Jochen <97750753+Jochengehtab@users.noreply.github.com> Date: Tue, 5 May 2026 19:24:47 +0200 Subject: [PATCH 31/42] fix printing and add pow function --- src/CMakeLists.txt | 4 +- src/vec/base256.cpp | 19 ++----- src/vec/base256.h | 36 +++++++++--- tests/test_base256.cpp | 124 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 159 insertions(+), 24 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1572ac9..a6ca4d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,4 +4,6 @@ project(RSA-Encryptor CXX) add_executable(RSA-Encryptor main.cpp cli.cpp) add_subdirectory(core) -add_subdirectory(vec) \ No newline at end of file +add_subdirectory(vec) + +target_link_libraries(RSA-Encryptor PRIVATE RSA-Vec) \ No newline at end of file diff --git a/src/vec/base256.cpp b/src/vec/base256.cpp index ebf0c98..d2ebce8 100644 --- a/src/vec/base256.cpp +++ b/src/vec/base256.cpp @@ -3,7 +3,6 @@ #include void operations::Base256::add(const ByteArray &b) noexcept { - // Time complexity O(iterations) // Initialize the result vector to store the sum ByteArray result; @@ -97,7 +96,6 @@ void operations::Base256::sub(const ByteArray &b) noexcept { } void operations::Base256::mul(const ByteArray &b) noexcept { - // Time complexity O(aSize * bSize) const std::uint64_t aSize = data.size(); const std::uint64_t bSize = b.size(); @@ -125,7 +123,7 @@ void operations::Base256::mul(const ByteArray &b) noexcept { result[i + bSize] += static_cast(carry); } - // Since aSize + bSize typically provides extra buffering, normalise the number safely + // Since aSize + bSize typically provides extra buffering, normalize the number safely normalizeVector(result); data = std::move(result); @@ -138,7 +136,7 @@ void operations::Base256::div(const ByteArray &divisor, ByteArray *remaining) no return; } - std::int64_t initialDividendIndex = getStartBitIndex(data); + const std::int64_t initialDividendIndex = getStartBitIndex(data); if (initialDividendIndex < 0) { if (remaining != nullptr) *remaining = {0}; data = {0}; @@ -184,16 +182,11 @@ void operations::Base256::div(const ByteArray &divisor, ByteArray *remaining) no data = std::move(quotient); } -[[nodiscard]] ByteArray operations::Base256::pow(const ByteArray &a, - const std::uint64_t &pow) noexcept { - // Copy the value from an into result while keeping a constant - ByteArray result; - std::copy(a.begin(), a.end(), std::back_inserter(result)); +operations::Base256 operations::Base256::pow(const Base256 &a, const std::uint64_t &pow) { + Base256 result(1); - // Start the loop at 1, because the first number is already assigned to result - for (std::uint32_t i = 1; i < pow; i++) { - // TODO - // result = mul(result, a); + for (std::uint64_t i = 0; i < pow; ++i) { + result *= a; } return result; diff --git a/src/vec/base256.h b/src/vec/base256.h index 2c50bce..95047b3 100644 --- a/src/vec/base256.h +++ b/src/vec/base256.h @@ -1,6 +1,7 @@ #ifndef VEC_OPERATIONS_H #define VEC_OPERATIONS_H +#include #include #include @@ -25,18 +26,39 @@ class Base256 { void div(const ByteArray &divisor, ByteArray *remaining = nullptr) noexcept; - ByteArray pow(const ByteArray &a, const std::uint64_t &pow) noexcept; + static Base256 pow(const Base256 &a, const std::uint64_t &pow) ; + void print() const { - std::uint64_t value = 0; - // Vector stores data natively as little-endian, iterate using reverse iterator - for (auto it = data.rbegin(); it != data.rend(); ++it) { - value = (value << 8) | static_cast(*it); + // Make a copy because we will modify it + ByteArray temp = data; + + // Handle zero explicitly + if (isZero(temp)) { + std::cout << "0" << std::endl; + return; + } + + std::string result; + + while (!isZero(temp)) { + std::uint16_t remainder = 0; + + // Divide temp by 10 (base256 -> base10 conversion step) + for (std::int64_t i = static_cast(temp.size()) - 1; i >= 0; --i) { + const std::uint16_t current = (remainder << 8) | temp[i]; + temp[i] = static_cast(current / 10); + remainder = current % 10; + } + + result.push_back(static_cast('0' + remainder)); } - std::cout << value << '\n'; + + std::ranges::reverse(result); + std::cout << result << std::endl; } - void normalizeVector(ByteArray &a) const { + static void normalizeVector(ByteArray &a) { while (a.size() > 1 && a.back() == 0) { a.pop_back(); } diff --git a/tests/test_base256.cpp b/tests/test_base256.cpp index 322a404..2d2c127 100644 --- a/tests/test_base256.cpp +++ b/tests/test_base256.cpp @@ -1,16 +1,16 @@ #include -#include #include #include +#include #include "../src/vec/base256.h" using operations::Base256; -static Base256 make(uint64_t v) { return Base256(v); } +static Base256 make(const uint64_t v) { return Base256(v); } // Helper for maximum uint64_t value -const uint64_t MAX_U64 = std::numeric_limits::max(); +constexpr uint64_t MAX_U64 = std::numeric_limits::max(); TEST_CASE("Base256: constructors, copy, assignment, self-assignment") { SECTION("Default is zero") { @@ -279,3 +279,121 @@ TEST_CASE("Base256: comparisons edge cases") { REQUIRE(max64 < overflow); } } + +TEST_CASE("Base256: print outputs base10 string to cout") { + // Helper lambda to capture std::cout output safely + auto capturePrint =[](const Base256 &num) { + std::stringstream buffer; + // Redirect std::cout to our buffer + std::streambuf *oldCout = std::cout.rdbuf(buffer.rdbuf()); + num.print(); + // Restore std::cout to its original state + std::cout.rdbuf(oldCout); + return buffer.str(); + }; + + SECTION("Absolute zero") { + REQUIRE(capturePrint(make(0)) == "0\n"); + } + + SECTION("Single digit / small numbers") { + REQUIRE(capturePrint(make(7)) == "7\n"); + REQUIRE(capturePrint(make(42)) == "42\n"); + } + + SECTION("Byte boundary overlaps") { + REQUIRE(capturePrint(make(255)) == "255\n"); + REQUIRE(capturePrint(make(256)) == "256\n"); + REQUIRE(capturePrint(make(65535)) == "65535\n"); + REQUIRE(capturePrint(make(65536)) == "65536\n"); + } + + SECTION("Standard large uint64_t numbers") { + REQUIRE(capturePrint(make(1000000000)) == "1000000000\n"); + // MAX_U64 + REQUIRE(capturePrint(make(MAX_U64)) == "18446744073709551615\n"); + } + + SECTION("Extreme cases: Exceeding uint64_t limitations") { + // MAX_U64 + 1 + Base256 overflowPlusOne = make(MAX_U64) + make(1); + REQUIRE(capturePrint(overflowPlusOne) == "18446744073709551616\n"); + + // MAX_U64 * 10 + Base256 overflowTimesTen = make(MAX_U64) * make(10); + REQUIRE(capturePrint(overflowTimesTen) == "184467440737095516150\n"); + } +} + +TEST_CASE("Base256: power edge cases") { + // Test 255^2 (Should be 65025, or [0x01, 0xFE] in little-endian) + Base256 res = Base256::pow(Base256(255), 2); + REQUIRE(res == Base256(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 = Base256::pow(Base256(2), 16); + REQUIRE(largePower == Base256(65536)); +} + +TEST_CASE("Base256: pow (exponentiation)") { + SECTION("Power of zero (x^0 = 1)") { + REQUIRE(Base256::pow(make(10), 0) == make(1)); + REQUIRE(Base256::pow(make(255), 0) == make(1)); + REQUIRE(Base256::pow(make(MAX_U64), 0) == make(1)); + // Mathematical convention for programming: 0^0 = 1 + REQUIRE(Base256::pow(make(0), 0) == make(1)); + } + + SECTION("Power of one (x^1 = x)") { + REQUIRE(Base256::pow(make(0), 1) == make(0)); + REQUIRE(Base256::pow(make(42), 1) == make(42)); + REQUIRE(Base256::pow(make(MAX_U64), 1) == make(MAX_U64)); + } + + SECTION("Zero to the power of X (0^x = 0)") { + REQUIRE(Base256::pow(make(0), 2) == make(0)); + REQUIRE(Base256::pow(make(0), 100) == make(0)); + } + + SECTION("One to the power of X (1^x = 1)") { + REQUIRE(Base256::pow(make(1), 2) == make(1)); + REQUIRE(Base256::pow(make(1), 100) == make(1)); + } + + SECTION("Standard combinations") { + REQUIRE(Base256::pow(make(2), 2) == make(4)); + REQUIRE(Base256::pow(make(2), 8) == make(256)); + REQUIRE(Base256::pow(make(2), 16) == make(65536)); + REQUIRE(Base256::pow(make(10), 3) == make(1000)); + REQUIRE(Base256::pow(make(5), 4) == make(625)); + } + + 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 = Base256::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 = Base256::pow(make(10), 20); + + std::stringstream buffer; + std::streambuf *oldCout = std::cout.rdbuf(buffer.rdbuf()); + tenTo20.print(); + std::cout.rdbuf(oldCout); + + REQUIRE(buffer.str() == "100000000000000000000\n"); + } + + 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 = Base256::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)); + } +} From 122cde6349c6dadcf83ef7de9fce91156dd9be59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= <134922046+LordofGhost@users.noreply.github.com> Date: Tue, 5 May 2026 22:41:57 +0200 Subject: [PATCH 32/42] Refactor CMake configuration and move CLI module --- src/CMakeLists.txt | 7 ++++--- src/cli/CMakeLists.txt | 3 +++ src/{ => cli}/cli.cpp | 2 +- src/{ => cli}/cli.h | 0 src/core/CMakeLists.txt | 2 -- src/main.cpp | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 src/cli/CMakeLists.txt rename src/{ => cli}/cli.cpp (92%) rename src/{ => cli}/cli.h (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a6ca4d4..ac1a677 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,10 @@ cmake_minimum_required(VERSION 3.20) project(RSA-Encryptor CXX) -add_executable(RSA-Encryptor main.cpp cli.cpp) - add_subdirectory(core) add_subdirectory(vec) +add_subdirectory(cli) + +add_executable(RSA-Encryptor main.cpp) -target_link_libraries(RSA-Encryptor PRIVATE RSA-Vec) \ No newline at end of file +target_link_libraries(RSA-Encryptor PRIVATE RSA-Vec RSA-Core RSA-CLI) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt new file mode 100644 index 0000000..e6e6c8f --- /dev/null +++ b/src/cli/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(RSA-CLI + cli.cpp +) \ No newline at end of file diff --git a/src/cli.cpp b/src/cli/cli.cpp similarity index 92% rename from src/cli.cpp rename to src/cli/cli.cpp index 0fd1f51..cb59c38 100644 --- a/src/cli.cpp +++ b/src/cli/cli.cpp @@ -2,7 +2,7 @@ #include -#include "core/key.h" +#include "../core/key.h" void cli::help() { std::cout << "Welcome to RSA-Encryptor" << std::endl; } diff --git a/src/cli.h b/src/cli/cli.h similarity index 100% rename from src/cli.h rename to src/cli/cli.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a9c5c95..61bf5ad 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -3,5 +3,3 @@ add_library(RSA-Core key.cpp encryption.cpp ) -target_include_directories(RSA-Core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(RSA-Encryptor PRIVATE RSA-Core) diff --git a/src/main.cpp b/src/main.cpp index 8d4c308..0f0411c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,7 @@ #include #include -#include "cli.h" +#include "cli/cli.h" #include "core/key.h" int main(int argc, char *argv[]) { From 9c236b82e98247769e2bfe2b789195038ec17bf2 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 08:23:59 +0200 Subject: [PATCH 33/42] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index b688f8f..cf46c7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .idea /build -/src/build /Testing /cmake-build-debug .vscode From 2b9629875db0fef816f1ad203b1c6f6d8c63f76f Mon Sep 17 00:00:00 2001 From: SecondJochen <213428453+SecondJochen@users.noreply.github.com> Date: Wed, 6 May 2026 06:26:57 +0000 Subject: [PATCH 34/42] Apply Clang formatting --- src/vec/base256.h | 5 ++--- tests/test_base256.cpp | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vec/base256.h b/src/vec/base256.h index 95047b3..c385059 100644 --- a/src/vec/base256.h +++ b/src/vec/base256.h @@ -26,8 +26,7 @@ class Base256 { void div(const ByteArray &divisor, ByteArray *remaining = nullptr) noexcept; - static Base256 pow(const Base256 &a, const std::uint64_t &pow) ; - + static Base256 pow(const Base256 &a, const std::uint64_t &pow); void print() const { // Make a copy because we will modify it @@ -58,7 +57,7 @@ class Base256 { std::cout << result << std::endl; } - static void normalizeVector(ByteArray &a) { + static void normalizeVector(ByteArray &a) { while (a.size() > 1 && a.back() == 0) { a.pop_back(); } diff --git a/tests/test_base256.cpp b/tests/test_base256.cpp index 2d2c127..0549861 100644 --- a/tests/test_base256.cpp +++ b/tests/test_base256.cpp @@ -282,7 +282,7 @@ TEST_CASE("Base256: comparisons edge cases") { TEST_CASE("Base256: print outputs base10 string to cout") { // Helper lambda to capture std::cout output safely - auto capturePrint =[](const Base256 &num) { + auto capturePrint = [](const Base256 &num) { std::stringstream buffer; // Redirect std::cout to our buffer std::streambuf *oldCout = std::cout.rdbuf(buffer.rdbuf()); @@ -292,9 +292,7 @@ TEST_CASE("Base256: print outputs base10 string to cout") { return buffer.str(); }; - SECTION("Absolute zero") { - REQUIRE(capturePrint(make(0)) == "0\n"); - } + SECTION("Absolute zero") { REQUIRE(capturePrint(make(0)) == "0\n"); } SECTION("Single digit / small numbers") { REQUIRE(capturePrint(make(7)) == "7\n"); From 97d8e9432f91a664353b9f64b4fbf297cefaa68e Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 08:36:20 +0200 Subject: [PATCH 35/42] fix linking error --- src/cli/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index e6e6c8f..b1f7bf5 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,3 +1,8 @@ add_library(RSA-CLI cli.cpp +) + +target_link_libraries(RSA-CLI + PRIVATE + RSA-Core ) \ No newline at end of file From 633d54de3b898e73f78780a18006fa1de277ade6 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 09:08:34 +0200 Subject: [PATCH 36/42] move algorithms into subdirectory --- docs/{ => algorithms}/add.md | 0 docs/{ => algorithms}/div.md | 0 docs/{ => algorithms}/mul.md | 0 docs/{ => algorithms}/sub.md | 0 docs/base256.md | 8 ++++---- 5 files changed, 4 insertions(+), 4 deletions(-) rename docs/{ => algorithms}/add.md (100%) rename docs/{ => algorithms}/div.md (100%) rename docs/{ => algorithms}/mul.md (100%) rename docs/{ => algorithms}/sub.md (100%) diff --git a/docs/add.md b/docs/algorithms/add.md similarity index 100% rename from docs/add.md rename to docs/algorithms/add.md diff --git a/docs/div.md b/docs/algorithms/div.md similarity index 100% rename from docs/div.md rename to docs/algorithms/div.md diff --git a/docs/mul.md b/docs/algorithms/mul.md similarity index 100% rename from docs/mul.md rename to docs/algorithms/mul.md diff --git a/docs/sub.md b/docs/algorithms/sub.md similarity index 100% rename from docs/sub.md rename to docs/algorithms/sub.md diff --git a/docs/base256.md b/docs/base256.md index 98e918c..6c2f3fa 100644 --- a/docs/base256.md +++ b/docs/base256.md @@ -36,10 +36,10 @@ The class enforces strict **Array Normalization**. After every operation, traili ## Algorithmic Mechanics -- [Addition](add.md#addition) -- [Subtraction](sub.md#subtraction) -- [Subtraction](mul.md#multiplication) -- [Subtraction](div.md#division) +- [Addition](algorithms/add.md#addition) +- [Subtraction](algorithms/sub.md#subtraction) +- [Subtraction](algorithms/mul.md#multiplication) +- [Subtraction](algorithms/div.md#division) --- ## Code Examples & Usage From ad0c3066e9db00c4aa7dec22fb74d3e825d0882a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= <134922046+LordofGhost@users.noreply.github.com> Date: Wed, 6 May 2026 11:14:42 +0200 Subject: [PATCH 37/42] refactor cmake: Tests only run in Debug build --- CMakeLists.txt | 10 +++++++++- tests/CMakeLists.txt | 37 +++++++++++++++---------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 97fbf45..f7e5bbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,4 +7,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_subdirectory(src) -add_subdirectory(tests) \ No newline at end of file + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + include(CTest) + if (BUILD_TESTING) + add_subdirectory(tests) + endif() +else() + message(STATUS "Skipping CTest/tests because CMAKE_BUILD_TYPE != Debug") +endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 306a703..96d5720 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,7 @@ - # Prevent CMake from re-downloading Catch2 every time CLion reloads set(FETCHCONTENT_UPDATES_DISCONNECTED ON CACHE BOOL "Don't update FetchContent on every config" FORCE) -# include(CTest) automatically sets BUILD_TESTING to ON by default -include(CTest) - -# Catch2 via FetchContent +# Download Catch2 include(FetchContent) FetchContent_Declare( Catch2 @@ -18,21 +14,18 @@ FetchContent_MakeAvailable(Catch2) list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) include(Catch) +add_executable(RSA-Encryptor-Tests + test_base256.cpp +) +target_link_libraries(RSA-Encryptor-Tests + PRIVATE + RSA-Core + RSA-Vec + Catch2::Catch2WithMain +) -if (BUILD_TESTING) - add_executable(RSA-Encryptor-Tests - ../tests/test_base256.cpp - ) - target_include_directories(RSA-Encryptor-Tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(RSA-Encryptor-Tests - PRIVATE - RSA-Core - RSA-Vec - Catch2::Catch2WithMain - ) - - catch_discover_tests(RSA-Encryptor-Tests - TEST_PREFIX base256: - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - ) -endif () +# Catch2 integration in CTest +catch_discover_tests(RSA-Encryptor-Tests + TEST_PREFIX base256: + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) From b16dbf1c09e08bd611bfe5b4d743f3950218f6b9 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 12:36:07 +0200 Subject: [PATCH 38/42] new div algo version --- docs/algorithms/div.md | 36 ++++++++++++++++--------------- src/vec/base256.cpp | 49 ++++++++++++++++-------------------------- src/vec/helper.h | 4 ++-- src/vec/types.h | 1 + tests/test_base256.cpp | 45 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 49 deletions(-) diff --git a/docs/algorithms/div.md b/docs/algorithms/div.md index f909d45..cc18030 100644 --- a/docs/algorithms/div.md +++ b/docs/algorithms/div.md @@ -16,8 +16,8 @@ Binary Long Division works exactly like the long division taught in elementary s 4. If it doesn't fit, write `0` to the quotient, don't subtract, and "bring down" the next bit. **Quick Binary Example `7 / 2`:** -- `111` (7 in decimal) -- `10` (2 in decimal) +- `7` (`111`) +- `2` (`10`) **The Setup:** ```text @@ -64,19 +64,22 @@ Divisor: 10 | 1 1 1 <-- Dividend ``` **Result:** -Reading the top from left to right, our built quotient is **`011`** (`3` in decimal). The leftover value at the bottom is our remainder: **`1`**. +The quotient is `001` (`3` in decimal). The leftover value at the bottom is our remainder: **`1`**. *(7 / 2 = 3 R 1)*. -## 1. Edge Cases and Initialization -1. **Division by Zero:** If the divisor is `0`, the operation safely aborts, defaulting the quotient and remainder to `0`. -2. **Finding the MSB (`getStartBitIndex`):** The algorithm scans the dividend to find the exact global index of the highest `1` bit (`initialDividendIndex`). This prevents the algorithm from processing leading mathematical "ghost zeros." -3. **Preallocation:** Because the exact bit-length of the quotient correlates directly to the `initialDividendIndex`, the `quotient` vector is pre-allocated to its exact maximum required size (`(initialDividendIndex / 8) + 1`). -## 2. The Working Remainder (`dividendMask`) -The algorithm initializes the `dividendMask` by taking a value of `0`, shifting it left by 1, and "bringing down" the absolute highest bit from the dividend. +## Implementation +The division algorithm is implement within 5 steps. +### 1. Initialization +1. **Division by Zero:** If the divisor is `0`, we abort and default the quotient and remainder to `0`. +2. **Finding the MSB** Using the `getStartBitIndex` function we get the MSB and assign it to `initialDividendIndex`. This ensures that we don't process any ghost zeros because the highest byte could look something like this `00010001`. +3. **Preallocation:** Because the exact bit-length of the quotient correlates directly to the `initialDividendIndex`, the `quotient` vector is pre-allocated to the maximum required size (`(initialDividendIndex / 8) + 1`). -## 3. The Bitwise Evaluation Loop -The algorithm enters a `while` loop, processing bit by bit from `initialDividendIndex` down to `-1`. +### 2. Working Remainder (`dividendMask`) +`dividendMask` is initialized by taking a value of `0`, shifting it left by 1, and "bringing down" the absolute highest bit from the **dividend**. + +### 3. Bitwise Evaluation Loop +In a `while` loop, bit by bit is processed from `initialDividendIndex` down to `-1`. For every bit position, the mathematical power index (`currentQBitIndex`) is evaluated. At each step we check if the `dividendMask` (working remainder) is $\ge$ the `divisor`. @@ -92,21 +95,20 @@ At each step we check if the `dividendMask` (working remainder) is $\ge$ the `di * No subtraction occurs. * The `dividendMask` is simply shifted left by 1, and the *next* bit from the dividend is brought down to increase the value of the mask for the next loop iteration. -## 4. Modulo (The Remainder) +### 4. Modulo (The Remainder) Because this is integer division, there is often a fractional remainder. The `div` function accepts an optional pointer to a `remaining` vector (`Bytes* remaining`). When the loop finishes processing the final bit (`dividendIndex < 0`), whatever mathematical value is left inside the `dividendMask` is exactly the modulo. If the pointer is provided, the mask is copied into it. *(Note: Because of this architecture, evaluating `A % B` requires the exact same computational effort as `A / B`. Therefore, if both the quotient and remainder are needed, they are extracted simultaneously to halve CPU cycles).* -## 5. Final Normalization +### 5. Final Normalization Even though pre-allocation is tightly bound to the `initialDividendIndex`, the final quotient might have leading zeros depending on the magnitude of the divisor. The `div` function concludes by stripping any trailing zero-bytes from the little-endian vector to maintain strict `Base256` normalization guarantees. --- -## Visualization: `13 / 3` (Binary Long Division) -To understand how the bitwise loop processes numbers, let's trace the division of `13` by `3`. -* **Dividend:** `13` $\rightarrow$ Binary: `1101` -* **Divisor:** `3` $\rightarrow$ Binary: `0011` +## Visualization: `13 / 3` +* **Dividend:** `13` (`1101`) +* **Divisor:** `3` (`0011`) **Initialization:** * `getStartBitIndex(1101)` returns `3` (the 0-based index of the highest `1` bit). diff --git a/src/vec/base256.cpp b/src/vec/base256.cpp index d2ebce8..0e69b84 100644 --- a/src/vec/base256.cpp +++ b/src/vec/base256.cpp @@ -130,53 +130,42 @@ void operations::Base256::mul(const ByteArray &b) noexcept { } void operations::Base256::div(const ByteArray &divisor, ByteArray *remaining) noexcept { - if (isZero(divisor)) { - data = {0}; - if (remaining != nullptr) *remaining = {0}; - return; - } - const std::int64_t initialDividendIndex = getStartBitIndex(data); - if (initialDividendIndex < 0) { + + if (isZero(divisor) || initialDividendIndex == INVALID_START_BIT_INDEX) { if (remaining != nullptr) *remaining = {0}; data = {0}; return; } - // Vector preallocated to support max potential bits mapped by initial index + // Pre-allocate quotient based on the bit length of the dividend ByteArray quotient((initialDividendIndex / 8) + 1, 0); + ByteArray dividendMask = {0}; - std::int64_t dividendIndex = initialDividendIndex; - ByteArray dividendMask = addBitFromNumber({0}, data, dividendIndex--); - - while (dividendIndex >= -1) { - // Evaluate mathematical power index representing current bit generated - std::int64_t currentQBitIndex = dividendIndex + 1; + for (std::int64_t i = initialDividendIndex; i >= 0; --i) { + // "Bring down" the next bit from the dividend into our working mask + dividendMask = addBitFromNumber(dividendMask, data, i); + // Check if the divisor fits into the current working mask if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { - // Drop evaluating bit immediately at explicitly targeted position inside quotient - quotient[currentQBitIndex / 8] |= (1 << (currentQBitIndex % 8)); - if (dividendIndex < 0) { - if (remaining != nullptr) *remaining = sub(dividendMask, divisor); - break; - } + // Set the corresponding bit in the quotient + quotient[i / 8] |= (1 << (i % 8)); + // Subtract the divisor from the working mask dividendMask = sub(dividendMask, divisor); - dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); - } else { - if (dividendIndex < 0) { - if (remaining != nullptr) *remaining = dividendMask; - break; - } - - dividendMask = addBitFromNumber(dividendMask, data, dividendIndex--); } } - // Strip trailing normalization zeros (empty space buffers) securely - normalizeVector(quotient); + // Whatever is left in the mask after processing all bits is the remainder + if (remaining != nullptr) { + *remaining = std::move(dividendMask); + normalizeVector(*remaining); + if (remaining->empty()) remaining->push_back(0); + } + // 6. Finalize the Quotient + normalizeVector(quotient); if (quotient.empty()) quotient.push_back(0); data = std::move(quotient); diff --git a/src/vec/helper.h b/src/vec/helper.h index f4b80a7..2e17c67 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -10,12 +10,12 @@ [[nodiscard]] inline std::int64_t getStartBitIndex(const ByteArray &a) { // Handle the absolute zero edge-case if (a.empty() || (a.size() == 1 && a[0] == 0)) { - return -1; + return INVALID_START_BIT_INDEX; } // Because of normalization, the highest bit is guaranteed // to be in the very last byte of the vector. - const std::int64_t highestByteIndex = a.size() - 1; + const auto highestByteIndex = static_cast(a.size() - 1); const std::uint8_t highestByte = a.back(); // Find the highest bit in just this one byte (max 8 iterations) diff --git a/src/vec/types.h b/src/vec/types.h index 24686ef..d6b8e4d 100644 --- a/src/vec/types.h +++ b/src/vec/types.h @@ -4,5 +4,6 @@ #include using ByteArray = std::vector; +constexpr int INVALID_START_BIT_INDEX = -1; #endif // RSA_ENCRYPTOR_TYPES_H diff --git a/tests/test_base256.cpp b/tests/test_base256.cpp index 0549861..a414ae8 100644 --- a/tests/test_base256.cpp +++ b/tests/test_base256.cpp @@ -212,6 +212,51 @@ TEST_CASE("Base256: division & compound division") { } } +TEST_CASE("Base256: 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)); + REQUIRE(make(MAX_U64) / make(0) == make(0)); + } + + SECTION("Dividing zero by zero yields zero") { + REQUIRE(make(0) / make(0) == make(0)); + } +} + +// TODO +TEST_CASE("Base256: 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 + + /* + SECTION("Remainder of zero dividend yields zero") { + // Tests the interior block: `if (remaining != nullptr) *remaining = {0};` + // when `initialDividendIndex < 0` + REQUIRE(make(0) % make(123456) == make(0)); + } + + + SECTION("Remainder of division by zero yields zero") { + // Tests the interior block: `if (remaining != nullptr) *remaining = {0};` + // when `isZero(divisor)` + REQUIRE(make(123456) % make(0) == make(0)); + } + + SECTION("Normal fractional remainders") { + // Validates standard operation of the `remaining` pointer during `div` + REQUIRE(make(10) % make(3) == make(1)); + REQUIRE(make(255) % make(2) == make(1)); + REQUIRE(make(1000) % make(333) == make(1)); + } + + SECTION("Dividend smaller than divisor yields the dividend as remainder") { + REQUIRE(make(10) % make(20) == make(10)); + REQUIRE(make(1) % make(MAX_U64) == make(1)); + } + */ +} + TEST_CASE("Base256: mixed expressions & combinations") { SECTION("(a + b) * c then divide back by c (c > 0)") { for (uint64_t a = 1; a <= 20; ++a) From 988afabdfb114a884a9c52b509c017c62ad09ef4 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 12:41:39 +0200 Subject: [PATCH 39/42] Update add.md --- docs/algorithms/add.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/algorithms/add.md b/docs/algorithms/add.md index 6a8849b..d666f2d 100644 --- a/docs/algorithms/add.md +++ b/docs/algorithms/add.md @@ -2,7 +2,7 @@ The addition algorithm uses a `uint16_t` accumulator to detect overflows beyond the 8-bit boundary (255). 1. **Iteration:** The algorithm traverses from the LSB (`index 0`) to the MSB (`index N`). -2. **Carry Propagation:** At each position `i`, it calculates: +2. **Carry Propagation:** For each byte, represented by `i`, it calculates: `sum = a[i] + b[i] + carry` 3. **Overflow Handling:** The carry for the next iteration is determined by `sum >> 8`. The digit stored in the current position is determined by `sum & 0xFF`. 4. **Finalization:** If a carry remains after the final byte is processed, an additional byte is pushed to the vector, extending the total magnitude of the number. \ No newline at end of file From bd29151382a262e3547241a25e6053839936ca90 Mon Sep 17 00:00:00 2001 From: SecondJochen Date: Wed, 6 May 2026 14:31:34 +0200 Subject: [PATCH 40/42] return to old algo --- src/vec/base256.cpp | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/vec/base256.cpp b/src/vec/base256.cpp index 0e69b84..f298633 100644 --- a/src/vec/base256.cpp +++ b/src/vec/base256.cpp @@ -131,41 +131,47 @@ void operations::Base256::mul(const ByteArray &b) noexcept { void operations::Base256::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}; data = {0}; return; } - // Pre-allocate quotient based on the bit length of the dividend + // Pre allocate vector ByteArray quotient((initialDividendIndex / 8) + 1, 0); - ByteArray dividendMask = {0}; - for (std::int64_t i = initialDividendIndex; i >= 0; --i) { - // "Bring down" the next bit from the dividend into our working mask - dividendMask = addBitFromNumber(dividendMask, data, i); + std::int64_t dividendIndex = initialDividendIndex; + ByteArray dividendMask = addBitFromNumber({0}, data, dividendIndex--); + + while (dividendIndex >= -1) { + std::int64_t currentQBitIndex = dividendIndex + 1; - // Check if the divisor fits into the current working mask if (isEqual(dividendMask, divisor) || isBigger(dividendMask, divisor)) { + // Drop evaluating bit immediately at explicitly targeted position inside quotient + quotient[currentQBitIndex / 8] |= (1 << (currentQBitIndex % 8)); - // Set the corresponding bit in the quotient - quotient[i / 8] |= (1 << (i % 8)); + if (dividendIndex < 0) { + if (remaining != nullptr) *remaining = sub(dividendMask, divisor); + break; + } - // Subtract the divisor from the working mask dividendMask = sub(dividendMask, divisor); - } - } + dividendMask = addBitFromNumber(dividendMask, data, dividendIndex); + dividendIndex--; + } else { + if (dividendIndex < 0) { + if (remaining != nullptr) *remaining = dividendMask; + break; + } - // Whatever is left in the mask after processing all bits is the remainder - if (remaining != nullptr) { - *remaining = std::move(dividendMask); - normalizeVector(*remaining); - if (remaining->empty()) remaining->push_back(0); + dividendMask = addBitFromNumber(dividendMask, data, dividendIndex); + dividendIndex--; + } } - // 6. Finalize the Quotient + // Strip trailing normalization zeros (empty space buffers) securely normalizeVector(quotient); + if (quotient.empty()) quotient.push_back(0); data = std::move(quotient); From db1de35ca23eff3119115987b5498bc33562776c Mon Sep 17 00:00:00 2001 From: SecondJochen <213428453+SecondJochen@users.noreply.github.com> Date: Wed, 6 May 2026 12:37:09 +0000 Subject: [PATCH 41/42] Apply Clang formatting --- tests/test_base256.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_base256.cpp b/tests/test_base256.cpp index a414ae8..cd031e6 100644 --- a/tests/test_base256.cpp +++ b/tests/test_base256.cpp @@ -219,9 +219,7 @@ TEST_CASE("Base256: division by zero (edge cases)") { REQUIRE(make(MAX_U64) / make(0) == make(0)); } - SECTION("Dividing zero by zero yields zero") { - REQUIRE(make(0) / make(0) == make(0)); - } + SECTION("Dividing zero by zero yields zero") { REQUIRE(make(0) / make(0) == make(0)); } } // TODO From 3cde2c47f7c8b089f9bd21175f11328c60d4db37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20B=C3=B6hm?= <134922046+LordofGhost@users.noreply.github.com> Date: Wed, 6 May 2026 14:45:05 +0200 Subject: [PATCH 42/42] Update CI configuration and helper function - Upgrade actions/checkout and actions/upload-artifact versions - Install Ninja build system for Linux, macOS, and Windows - Change date format in naming convention for artifacts - Add comment to helper function for clarity --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++-------- src/vec/helper.h | 1 + 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4689a0b..6dd610c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,12 +23,28 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: true - - name: Setup Ninja - uses: ashutoshvarma/setup-ninja@master + - name: Install Ninja (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y ninja-build + + - name: Install Ninja (macOS) + if: runner.os == 'macOS' + run: | + if ! command -v ninja >/dev/null 2>&1; then + brew install ninja + fi + + + - name: Install Ninja (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: choco install ninja -y - name: Configure CMake run: | @@ -52,7 +68,7 @@ jobs: - name: Upload Artifacts for Release if: matrix.build-configuration == 'Release' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.config.os }}-build path: ${{ matrix.config.os }}-build.zip @@ -70,10 +86,10 @@ jobs: contents: write steps: - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download all OS Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: ./release-artifacts merge-multiple: true # Puts the Mac, Win, and Linux zip files in the same folder @@ -83,9 +99,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Create naming convention - DATE=$(date +'%Y%m%d') + DATE=$(date +'%d_%m_%Y') SHORT_SHA=${GITHUB_SHA::8} - TAG_NAME="dev-${DATE}-${SHORT_SHA}" + TAG_NAME="dev-${SHORT_SHA}-${DATE}" # Create Pre-Release and attach all downloaded .zip files using modern GitHub CLI gh release create "$TAG_NAME" ./release-artifacts/*.zip \ diff --git a/src/vec/helper.h b/src/vec/helper.h index 2e17c67..38a333b 100644 --- a/src/vec/helper.h +++ b/src/vec/helper.h @@ -7,6 +7,7 @@ #include "types.h" +// This function returns the index of the most significant bit [[nodiscard]] inline std::int64_t getStartBitIndex(const ByteArray &a) { // Handle the absolute zero edge-case if (a.empty() || (a.size() == 1 && a[0] == 0)) {