/*******************************************************************************
 * Copyright (c) 2025, Jan Koester jan.koester@gmx.net
 * All rights reserved.
 * * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the <organization> nor the
 * names of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

#include <iostream>
#include <iomanip>
#include <algorithm>
#include <stdexcept>
#include <sstream>
#include <fstream>
#include <cstring>

#include "random.h"
#include "rsa.h"
#include "base64.h"

#if defined(_MSC_VER) && !defined(__clang__)
#define  __restrict__ __restrict
#endif // !__restrict__




namespace netplus {
    // Forward declarations
    static bool parseDERAndLoadRSA(const std::vector<uint8_t>& derData, netplus::rsa& out_rsa);
    static std::vector<uint8_t> pemKeyToDer(const std::string& pem);

    netplus::rsa::bigInt::bigInt(const bigInt& other)
    : used(other.used),
      capacity(other.capacity)
    {
        // 1. Allocate a new buffer of the same capacity
        if (capacity > 0) {
            data = std::make_unique<limb_t[]>(capacity);

            // 2. Perform a fast memory copy of the words
            // We only strictly need to copy up to 'other.used',
            // but copying the full capacity ensures 'ghost data' is consistent.
            std::copy(other.data.get(), other.data.get() + capacity, data.get());
        } else {
            data = nullptr;
        }
    }

    // Move constructor for bigInt
    netplus::rsa::bigInt::bigInt(bigInt&& other) noexcept
    : data(std::move(other.data)),
      used(other.used),
      capacity(other.capacity)
    {
        other.used = 0;
        other.capacity = 0;
    }

        // Assignment from buffer
    netplus::rsa::rsa(const std::vector<uint8_t>& buffer) {
        if (buffer.empty()) {
            throw std::runtime_error("Buffer is empty");
        }

        const std::vector<uint8_t>* derPtr = &buffer;
        std::vector<uint8_t> converted;

        // Auto-detect PEM format
        if (buffer.size() > 11 &&
            std::memcmp(buffer.data(), "-----BEGIN ", 11) == 0) {
            std::string pem(buffer.begin(), buffer.end());
            converted = pemKeyToDer(pem);
            derPtr = &converted;
        }

        if (!parseDERAndLoadRSA(*derPtr, *this)) {
            throw std::runtime_error("Failed to parse RSA key from DER buffer");
        }
    }

    // Assignment from filepath -- auto-detects PEM vs DER
    netplus::rsa::rsa(const std::string& filepath) {
        std::ifstream file(filepath, std::ios::binary | std::ios::ate);
        if (!file.is_open()) {
            throw std::runtime_error("Cannot open file: " + filepath);
        }

        std::streamsize fileSize = file.tellg();
        if (fileSize <= 0) {
            throw std::runtime_error("File is empty: " + filepath);
        }

        file.seekg(0, std::ios::beg);
        std::vector<uint8_t> rawData(static_cast<size_t>(fileSize));
        
        if (!file.read(reinterpret_cast<char*>(rawData.data()), fileSize)) {
            throw std::runtime_error("Failed to read file: " + filepath);
        }

        std::vector<uint8_t> derData;

        // Auto-detect PEM format
        if (rawData.size() > 11 &&
            std::memcmp(rawData.data(), "-----BEGIN ", 11) == 0) {
            std::string pem(rawData.begin(), rawData.end());
            derData = pemKeyToDer(pem);
        } else {
            derData = std::move(rawData);
        }

        if (!parseDERAndLoadRSA(derData, *this)) {
            throw std::runtime_error("Failed to parse RSA key from file: " + filepath);
        }
    }

    netplus::rsa::bigInt& netplus::rsa::bigInt::operator=(const bigInt& other) {
        if (this == &other) return *this; // Self-assignment check

        // 1. Reallocate if the current capacity is too small
        if (capacity < other.capacity) {
            data = std::make_unique<limb_t[]>(other.capacity);
            capacity = other.capacity;
        }

        // 2. Copy the state
        used = other.used;
        std::copy(other.data.get(), other.data.get() + used, data.get());

        // 3. Zero out the rest of the capacity for safety
        if (capacity > used) {
            std::fill(data.get() + used, data.get() + capacity, 0);
        }

        return *this;
    }

    // Move assignment for bigInt
    netplus::rsa::bigInt& netplus::rsa::bigInt::operator=(bigInt&& other) noexcept {
        if (this == &other) return *this;
        
        data = std::move(other.data);
        used = other.used;
        capacity = other.capacity;
        
        other.used = 0;
        other.capacity = 0;
        
        return *this;
    }

    void rsa::bigInt::shiftLeft(size_t n) {
        if (n == 0 || isZero()) return;

        size_t wordShift = n / LIMB_BITS;
        size_t bitShift  = n % LIMB_BITS;

        size_t newUsed = used + wordShift + (bitShift ? 1 : 0);
        reserve(newUsed);

        // Shift words and bits in-place, working from high end to low
        if (bitShift == 0) {
            // Pure word shift — move backwards to avoid overwriting
            for (size_t i = used; i-- > 0; )
                data[i + wordShift] = data[i];
        } else {
            // Combined word + bit shift
            for (size_t i = used; i-- > 0; ) {
                dlimb_t v = static_cast<dlimb_t>(data[i]) << bitShift;
                data[i + wordShift + 1] |= static_cast<limb_t>(v >> LIMB_BITS);
                data[i + wordShift] = static_cast<limb_t>(v);
            }
        }
        // Zero the low words freed by the shift
        for (size_t i = 0; i < wordShift; ++i)
            data[i] = 0;

        used = newUsed;
        while (used > 1 && data[used - 1] == 0) used--;
    }

    bool rsa::bigInt::isBitSet(size_t n) const {
        size_t wordIdx = n / LIMB_BITS;
        if (wordIdx >= used) return false;
        return (data[wordIdx] & (limb_t(1) << (n % LIMB_BITS))) != 0;
    }

    void netplus::rsa::bigInt::reserve(size_t new_cap) {
        if (new_cap <= capacity) return;
        auto new_data = std::make_unique<limb_t[]>(new_cap);
        if (data) {
            std::copy(data.get(), data.get() + used, new_data.get());
        }
        std::fill(new_data.get() + used, new_data.get() + new_cap, limb_t(0));
        data = std::move(new_data);
        capacity = new_cap;
    }

    void netplus::rsa::bigInt::shiftRight(size_t n) {
        if (n == 0 || isZero()) return;
        size_t wordShift = n / LIMB_BITS;
        size_t bitShift = n % LIMB_BITS;

        if (wordShift >= used) {
            used = 1; data[0] = 0;
            return;
        }

        for (size_t i = 0; i < used - wordShift; ++i) {
            limb_t val = data[i + wordShift];
            if (bitShift > 0 && i + wordShift + 1 < used) {
                val = (val >> bitShift) | (data[i + wordShift + 1] << (LIMB_BITS - bitShift));
            } else {
                val >>= bitShift;
            }
            data[i] = val;
        }
        used -= wordShift;
        while (used > 1 && data[used - 1] == 0) used--;
    }

    void rsa::bigInt::divideByTwo() {
        if (isZero()) return;
        for (size_t i = 0; i < used; ++i) {
            data[i] >>= 1;
            if (i + 1 < used && (data[i + 1] & 1)) {
                data[i] |= limb_t(1) << (LIMB_BITS - 1);
            }
        }
        if (used > 1 && data[used - 1] == 0) used--;
    }

    // Sets the bigInt value from a standard 64-bit integer
    void netplus::rsa::bigInt::fromUint64(uint64_t val) {
#if LIMB_BITS == 64
        reserve(1);
        data[0] = val;
        used = 1;
#else
        reserve(2);
        data[0] = (uint32_t)(val & 0xFFFFFFFFu);
        data[1] = (uint32_t)(val >> 32);
        used = (data[1] ? 2 : 1);
#endif
    }

    // Simple equality check for assertions without needing strings
    bool netplus::rsa::bigInt::equalsUint64(uint64_t val) const {
        if (val == 0) return isZero();
#if LIMB_BITS == 64
        if (used > 1) return false;
        return data[0] == val;
#else
        if (used > 2) return false;
        uint64_t current = data[0];
        if (used == 2) current |= (static_cast<uint64_t>(data[1]) << 32);
        return current == val;
#endif
    }

    std::string netplus::rsa::bigInt::toHexString() const {
        if (used == 0 || (used == 1 && data[0] == 0)) return "0";

        std::stringstream ss;
        ss << std::hex << std::uppercase;

        // First word no leading zeros
        ss << static_cast<uint64_t>(data[used - 1]);

        // Middle words padded
        for (int i = (int)used - 2; i >= 0; --i) {
            ss << std::setfill('0') << std::setw(LIMB_BITS / 4) << static_cast<uint64_t>(data[i]);
        }

        return ss.str();
    }

    rsa::bigInt rsa::bigInt::operator*(const rsa::bigInt& other) const {
        bigInt res(this->capacity + other.capacity);
        rsa::multiply(*this, other, res);
        return res;
    }

    rsa::bigInt rsa::bigInt::operator%(const rsa::bigInt& mod) const {
        bigInt q(this->capacity), r(mod.capacity);
        rsa::divide(*this, mod, q, r);
        return r;
    }


    rsa::rsa() : n(64), e(64), d(64) {
        // Explicitly initialize bigInt members with capacity 64
    }

    // Copy constructor
    rsa::rsa(const rsa& src)
    : n(src.n), e(src.e), d(src.d)
    {
    }

    // Move constructor
    rsa::rsa(rsa&& src) noexcept
    : n(std::move(src.n)), e(std::move(src.e)), d(std::move(src.d))
    {
    }

    // Copy assignment
    rsa& rsa::operator=(const rsa& src) {
        if (this != &src) {
            n = src.n;
            e = src.e;
            d = src.d;
        }
        return *this;
    }

    // Move assignment
    rsa& rsa::operator=(rsa&& src) noexcept {
        if (this != &src) {
            n = std::move(src.n);
            e = std::move(src.e);
            d = std::move(src.d);
        }
        return *this;
    }

    // Helper function to parse a DER length field
    static bool parseDERLength(const uint8_t* data, size_t maxLen, size_t& outLength, size_t& outBytesRead) {
        if (maxLen < 1) return false;
        
        outLength = data[0];
        outBytesRead = 1;
        
        // Long form
        if (outLength & 0x80) {
            int numOctets = static_cast<int>(outLength & 0x7F);
            if (numOctets <= 0 || numOctets > 4) return false;
            if (maxLen < 1 + static_cast<size_t>(numOctets)) return false;
            
            outLength = 0;
            for (int i = 0; i < numOctets; ++i) {
                outLength = (outLength << 8) | data[1 + i];
            }
            outBytesRead = 1 + numOctets;
        }
        
        return true;
    }

    // Helper function to parse a DER INTEGER
    static bool parseDERInteger(const uint8_t* data, size_t maxLen, std::vector<uint8_t>& outBytes, size_t& outBytesRead) {
        if (maxLen < 2 || data[0] != 0x02) {  // 0x02 = INTEGER tag
            return false;
        }
        
        size_t length = 0;
        size_t lenBytesRead = 0;
        if (!parseDERLength(data + 1, maxLen - 1, length, lenBytesRead)) {
            return false;
        }
        
        if (1 + lenBytesRead + length > maxLen) {
            return false;
        }
        
        const uint8_t* intData = data + 1 + lenBytesRead;
        outBytes.clear();
        
        // Skip leading zero byte if present (DER encoding convention for non-negative integers)
        if (length > 0 && intData[0] == 0x00) {
            outBytes.assign(intData + 1, intData + length);
        } else {
            outBytes.assign(intData, intData + length);
        }
        
        outBytesRead = 1 + lenBytesRead + length;
        return true;
    }

    // Helper function to check if buffer contains RSA OID (1.2.840.113549.1.1.1)
    static bool isRsaOid(const uint8_t* data, size_t len) {
        // RSA OID: 06 09 2a 86 48 86 f7 0d 01 01 01
        static const uint8_t rsaOid[] = {0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01};
        if (len < sizeof(rsaOid)) return false;
        return std::memcmp(data, rsaOid, sizeof(rsaOid)) == 0;
    }

    // Helper function to extract PKCS#1 key from PKCS#8 envelope
    static bool extractPKCS1FromPKCS8(const std::vector<uint8_t>& pkcs8Data, std::vector<uint8_t>& pkcs1Data) {
        // PKCS#8 PrivateKeyInfo structure:
        // SEQUENCE {
        //   INTEGER version (0)
        //   SEQUENCE { OID algorithm, NULL parameters }
        //   OCTET STRING privateKey  <-- this contains the PKCS#1 key
        // }
        
        if (pkcs8Data.size() < 20) return false;
        
        const uint8_t* data = pkcs8Data.data();
        size_t size = pkcs8Data.size();
        size_t cursor = 0;
        
        // Outer SEQUENCE
        if (data[cursor] != 0x30) return false;
        cursor++;
        
        size_t outerLen = 0, lenBytes = 0;
        if (!parseDERLength(data + cursor, size - cursor, outerLen, lenBytes)) return false;
        cursor += lenBytes;
        
        // Version INTEGER (should be 0)
        if (data[cursor] != 0x02) return false;
        std::vector<uint8_t> version;
        size_t intBytes = 0;
        if (!parseDERInteger(data + cursor, size - cursor, version, intBytes)) return false;
        cursor += intBytes;
        
        // Algorithm SEQUENCE
        if (data[cursor] != 0x30) return false;
        cursor++;
        
        size_t algLen = 0;
        if (!parseDERLength(data + cursor, size - cursor, algLen, lenBytes)) return false;
        cursor += lenBytes;
        
        // Check for RSA OID within algorithm sequence
        if (!isRsaOid(data + cursor, algLen)) return false;
        cursor += algLen;  // skip algorithm sequence content
        
        // Private key OCTET STRING
        if (data[cursor] != 0x04) return false;
        cursor++;
        
        size_t keyLen = 0;
        if (!parseDERLength(data + cursor, size - cursor, keyLen, lenBytes)) return false;
        cursor += lenBytes;
        
        if (cursor + keyLen > size) return false;
        
        // Extract the PKCS#1 key
        pkcs1Data.assign(data + cursor, data + cursor + keyLen);
        return true;
    }

    // Strip PEM envelope from RSA PRIVATE KEY / PRIVATE KEY / EC PRIVATE KEY
    static std::vector<uint8_t> pemKeyToDer(const std::string& pem) {
        // Find any "-----BEGIN ...-----" / "-----END ...-----" pair
        auto b = pem.find("-----BEGIN ");
        if (b == std::string::npos) return {};
        auto bEnd = pem.find("-----", b + 11);
        if (bEnd == std::string::npos) return {};
        bEnd += 5; // skip closing "-----"

        auto e = pem.find("-----END ", bEnd);
        if (e == std::string::npos) return {};

        std::string body = pem.substr(bEnd, e - bEnd);

        // Strip whitespace, keep only base64 chars
        std::string b64;
        b64.reserve(body.size());
        for (unsigned char c : body) {
            if (std::isalnum(c) || c == '+' || c == '/' || c == '=')
                b64.push_back(static_cast<char>(c));
        }

        netplus::base64 dec;
        dec.setEncodedData(b64);
        std::vector<char> decoded;
        dec >> decoded;

        std::vector<uint8_t> der;
        der.reserve(decoded.size());
        for (unsigned char c : decoded)
            der.push_back(static_cast<uint8_t>(c));
        return der;
    }

    // Helper function to parse DER data and load RSA key
    static bool parseDERAndLoadRSA(const std::vector<uint8_t>& derData, netplus::rsa& out_rsa) {
        if (derData.empty()) {
            return false;
        }
        
        // First, try to unwrap PKCS#8 envelope if present
        std::vector<uint8_t> pkcs1Data;
        if (extractPKCS1FromPKCS8(derData, pkcs1Data)) {
            // Recursively parse the extracted PKCS#1 key
            return parseDERAndLoadRSA(pkcs1Data, out_rsa);
        }
        
        const uint8_t* data = derData.data();
        size_t size = derData.size();
        size_t cursor = 0;
        
        // Must start with SEQUENCE (0x30)
        if (size < 2 || data[cursor] != 0x30) {
            return false;
        }
        cursor++;
        
        // Parse SEQUENCE length
        size_t seqLength = 0;
        size_t lenBytesRead = 0;
        if (!parseDERLength(data + cursor, size - cursor, seqLength, lenBytesRead)) {
            return false;
        }
        cursor += lenBytesRead;
        
        if (cursor + seqLength > size) {
            return false;
        }
        
        std::vector<uint8_t> n, e, d;
        
        // Try to parse as PKCS#1 RSAPrivateKey first
        size_t tempCursor = cursor;
        bool isPKCS1Private = false;
        
        // Check for version (INTEGER 0)
        if (tempCursor < cursor + seqLength && data[tempCursor] == 0x02) {
            std::vector<uint8_t> version;
            size_t intBytesRead = 0;
            if (parseDERInteger(data + tempCursor, seqLength - (tempCursor - cursor), version, intBytesRead)) {
                tempCursor += intBytesRead;
                
                // Try to parse n, e, d
                if (parseDERInteger(data + tempCursor, seqLength - (tempCursor - cursor), n, intBytesRead)) {
                    tempCursor += intBytesRead;
                    
                    if (parseDERInteger(data + tempCursor, seqLength - (tempCursor - cursor), e, intBytesRead)) {
                        tempCursor += intBytesRead;
                        
                        if (parseDERInteger(data + tempCursor, seqLength - (tempCursor - cursor), d, intBytesRead)) {
                            isPKCS1Private = true;
                        }
                    }
                }
            }
        }
        
        if (isPKCS1Private && !n.empty() && !e.empty() && !d.empty()) {
            // Successfully parsed PKCS#1 private key
            out_rsa.setRsaKeyFromRaw(n, e, d);
            return true;
        }
        
        // Try to parse as public key (might be SubjectPublicKeyInfo with BIT STRING, or just RSAPublicKey SEQUENCE)
        if (data[cursor] == 0x30) {
            // Could be either RSAPublicKey or algorithm identifier
            cursor++;
            size_t innerSeqLength = 0;
            if (!parseDERLength(data + cursor, size - cursor, innerSeqLength, lenBytesRead)) {
                return false;
            }
            cursor += lenBytesRead;
            
            if (cursor + innerSeqLength > size) {
                return false;
            }
            
            // Try to parse modulus and exponent
            size_t pubCursor = cursor;
            size_t intBytesRead = 0;
            if (parseDERInteger(data + pubCursor, innerSeqLength - (pubCursor - cursor), n, intBytesRead)) {
                pubCursor += intBytesRead;
                
                if (parseDERInteger(data + pubCursor, innerSeqLength - (pubCursor - cursor), e, intBytesRead)) {
                    if (!n.empty() && !e.empty()) {
                        // Create a public-only key
                        d.clear();  // No private exponent
                        out_rsa.setRsaKeyFromRaw(n, e, d);
                        return true;
                    }
                }
            }
        }
        
        return false;
    }

    // R = 2^(LIMB_BITS*nwords), so R2 = R*R mod N
    rsa::bigInt rsa::calculateR2Mod(const bigInt& mod) {
        bigInt r(mod.used * 4 + 2);
        r.fromUint64(1);
        r.shiftLeft(mod.used * LIMB_BITS * 2); // 2*(LIMB_BITS*nwords)
        bigInt q(r.used + 1), rem(mod.used + 1);
        divide(r, mod, q, rem);
        return rem;
    }

    void rsa::add(const bigInt& a, const bigInt& b, bigInt& res) {
        dlimb_t carry = 0;
        const size_t au = a.used;
        const size_t bu = b.used;
        size_t max_len = std::max(au, bu);

        res.reserve(max_len + 1);
        res.used = 0;

        const limb_t* ap = a.data.get();
        const limb_t* bp = b.data.get();
        limb_t* rp = res.data.get();

        for (size_t i = 0; i < max_len || carry; ++i) {
            dlimb_t sum = carry
                + (i < au ? (dlimb_t)ap[i] : 0)
                + (i < bu ? (dlimb_t)bp[i] : 0);

            rp[i] = (limb_t)sum;
            carry = sum >> LIMB_BITS;
            res.used++;
        }

        if (res.used == 0) { res.used = 1; rp[0] = 0; }
    }


    void rsa::multiply(const bigInt& a, const bigInt& b, bigInt& res) {
        const size_t au = a.used;
        const size_t bu = b.used;

        // Ensure result buffer has room for au+bu+1 limbs:
        // the carry propagation loop can temporarily write to index au+bu.
        res.reserve(au + bu + 1);

        const limb_t* __restrict__ ap = a.data.get();
        const limb_t* __restrict__ bp = b.data.get();
        limb_t* __restrict__ rp = res.data.get();

        // Zero the output region including the carry overflow slot
        std::memset(rp, 0, (au + bu + 1) * sizeof(limb_t));

        for (size_t i = 0; i < au; ++i) {
            dlimb_t carry = 0;
            const dlimb_t ai = (dlimb_t)ap[i];
            for (size_t j = 0; j < bu; ++j) {
                dlimb_t cur = (dlimb_t)rp[i + j] + ai * (dlimb_t)bp[j] + carry;
                rp[i + j] = (limb_t)cur;
                carry = cur >> LIMB_BITS;
            }

            size_t k = i + bu;
            dlimb_t cur = (dlimb_t)rp[k] + carry;
            rp[k] = (limb_t)cur;
            carry = cur >> LIMB_BITS;

            while (carry) {
                ++k;
                dlimb_t cur2 = (dlimb_t)rp[k] + carry;
                rp[k] = (limb_t)cur2;
                carry = cur2 >> LIMB_BITS;
            }
        }

        res.used = au + bu;
        while (res.used > 1 && rp[res.used - 1] == 0) res.used--;
    }


    int rsa::compare(const bigInt& a, const bigInt& b) {
        if (a.used != b.used)
            return (a.used > b.used) ? 1 : -1;

        const limb_t* ap = a.data.get();
        const limb_t* bp = b.data.get();
        for (int i = (int)a.used - 1; i >= 0; --i) {
            if (ap[i] != bp[i])
                return (ap[i] > bp[i]) ? 1 : -1;
        }
        return 0;
    }

    size_t rsa::bigInt::bitLength() const {
        if (isZero()) return 0;
        size_t bits = (used - 1) * LIMB_BITS;
        limb_t lastWord = data[used - 1];
        while (lastWord > 0) {
            lastWord >>= 1;
            bits++;
        }
        return bits;
    }

    void rsa::subtract(const bigInt& a, const bigInt& b, bigInt& res) {
        int64_t borrow = 0;
        const size_t au = a.used;
        const size_t bu = b.used;
        res.reserve(au);
        const limb_t* ap = a.data.get();
        const limb_t* bp = b.data.get();
        limb_t* rp = res.data.get();
        for (size_t i = 0; i < au; ++i) {
            // Use dlimb_t for borrow-safe subtraction
            dlimb_t av = ap[i];
            dlimb_t bv = (i < bu ? bp[i] : 0) + (dlimb_t)borrow;
            if (av >= bv) {
                rp[i] = (limb_t)(av - bv);
                borrow = 0;
            } else {
                rp[i] = (limb_t)((((dlimb_t)1 << LIMB_BITS) + av) - bv);
                borrow = 1;
            }
        }
        res.used = au;
        while (res.used > 1 && rp[res.used - 1] == 0) res.used--;
    }

    netplus::rsa::bigInt netplus::rsa::modPow(const netplus::rsa::bigInt& base,
                                    const netplus::rsa::bigInt& exp,
                                    const netplus::rsa::bigInt& mod)
    {
        using bigInt = netplus::rsa::bigInt;

        // Handle mod == 0 or 1 edge cases
        if (mod.used == 1 && mod.data[0] == 0) {
            throw std::runtime_error("modPow: modulus is zero");
        }
        if (mod.used == 1 && mod.data[0] == 1) {
            bigInt z(1U, 1);
            z.data[0] = 0;
            z.used = 1;
            return z;
        }

        // exp == 0 => 1 mod mod
        if (exp.isZero()) {
            bigInt one(1U, 1);
            bigInt q(one.capacity), r(mod.capacity);
            netplus::rsa::divide(one, mod, q, r);
            return r;
        }

        // Montgomery requires odd modulus
        if (mod.isEven()) {
            // Fallback to classic square-and-multiply if needed (not expected for RSA)
            bigInt result(1U, mod.capacity);
            bigInt a = base % mod;

            for (size_t i = exp.bitLength(); i-- > 0; ) {
                result = (result * result) % mod;
                if (exp.isBitSet(i)) result = (result * a) % mod;
            }
            return result;
        }

        // n_prime = -N^{-1} mod 2^LIMB_BITS (uses least significant limb)
        limb_t n_prime = netplus::rsa::calculateNPrime(mod.data[0]);

        // Helper: compute R^2 mod N where R = 2^(LIMB_BITS*mod.used)
        // Uses repeated doubling: start with 1, double 2*LIMB_BITS*mod.used times, mod N each time.
        // This avoids creating a huge intermediate integer.
        auto calculateR2Mod = [&](const bigInt& N) -> bigInt {
            bigInt r(N.used + 2);
            r.fromUint64(1);
            size_t totalBits = N.used * LIMB_BITS * 2; // need R^2 = 2^(2*LIMB_BITS*N.used)
            for (size_t i = 0; i < totalBits; ++i) {
                // r = r * 2 (shift left by 1)
                limb_t carry = 0;
                for (size_t w = 0; w < r.used; ++w) {
                    limb_t newCarry = r.data[w] >> (LIMB_BITS - 1);
                    r.data[w] = (r.data[w] << 1) | carry;
                    carry = newCarry;
                }
                if (carry) {
                    r.reserve(r.used + 1);
                    r.data[r.used] = carry;
                    r.used++;
                }
                // r = r mod N
                if (compare(r, N) >= 0) {
                    subtract(r, N, r);
                }
            }
            return r;
        };

        size_t n = mod.used;

        // Pre-allocate ALL scratch buffers once — no allocations inside the loop
        bigInt scratch(2 * n + 2);   // for montgomeryMultiply_into product
        bigInt temp(n + 2);          // for montgomeryMultiply_into output

        // Convert into Montgomery domain
        bigInt a = base % mod;
        bigInt R2 = calculateR2Mod(mod);

        bigInt a_bar(n + 2);
        montgomeryMultiply_into(a, R2, mod, n_prime, scratch, a_bar);

        // res_bar = 1 * R mod N  (Montgomery form of 1)
        bigInt one(1U, n + 1);
        bigInt res_bar(n + 2);
        montgomeryMultiply_into(one, R2, mod, n_prime, scratch, res_bar);

        // Exponentiation (left-to-right square-and-multiply) — zero allocations
        for (size_t i = exp.bitLength(); i-- > 0; ) {
            // res_bar = montMul(res_bar, res_bar) — square
            montgomeryMultiply_into(res_bar, res_bar, mod, n_prime, scratch, temp);
            std::swap(res_bar, temp);

            if (exp.isBitSet(i)) {
                // res_bar = montMul(res_bar, a_bar) — multiply
                montgomeryMultiply_into(res_bar, a_bar, mod, n_prime, scratch, temp);
                std::swap(res_bar, temp);
            }
        }

        // Convert out of Montgomery domain: montMul(res_bar, 1) = result
        montgomeryMultiply_into(res_bar, one, mod, n_prime, scratch, temp);
        return temp;
    }

    void rsa::divide(const bigInt& dividend, const bigInt& divisor, bigInt& quotient, bigInt& remainder) {
        remainder = dividend;
        quotient.reserve(dividend.used);
        std::fill(quotient.data.get(), quotient.data.get() + quotient.capacity, 0);
        quotient.used = 1;

        if (compare(dividend, divisor) < 0) return;

        bigInt tempDiv = divisor;
        int shift = (int)dividend.bitLength() - (int)divisor.bitLength();
        tempDiv.shiftLeft(shift);

        for (int i = 0; i <= shift; ++i) {
            if (compare(remainder, tempDiv) >= 0) {
                subtract(remainder, tempDiv, remainder);
                // Set the bit in the quotient
                int qBit = shift - i;
                quotient.data[qBit / LIMB_BITS] |= (limb_t(1) << (qBit % LIMB_BITS));
                if ((size_t)(qBit / LIMB_BITS) >= quotient.used) quotient.used = (qBit / LIMB_BITS) + 1;
            }
            tempDiv.shiftRight(1);
        }
        while (quotient.used > 1 && quotient.data[quotient.used - 1] == 0) quotient.used--;
    }

    std::vector<uint8_t> bigIntToBytes(const netplus::rsa::bigInt& n, size_t targetLen) {
        std::vector<uint8_t> result(targetLen, 0);
        // Copy the internal limb_t words into 8-bit bytes (Big Endian)
        constexpr size_t bytesPerLimb = LIMB_BITS / 8;
        for (size_t i = 0; i < n.used; ++i) {
            limb_t word = n.data[i];
            for (size_t j = 0; j < bytesPerLimb; ++j) {
                size_t bytePos = targetLen - 1 - (i * bytesPerLimb + j);
                if (bytePos < targetLen) {
                    result[bytePos] = (word >> (j * 8)) & 0xFF;
                }
            }
        }
        return result;
    }

    void netplus::rsa::generateSecureRandom(bigInt& n, size_t bits) {

        size_t words = (bits + LIMB_BITS - 1) / LIMB_BITS;

        n.reserve(words);
        n.used = words;

        fillRandomBytes(reinterpret_cast<uint8_t*>(n.data.get()),
                        words * sizeof(limb_t));

        n.data[words - 1] |= (limb_t(1) << ((bits - 1) % LIMB_BITS));

        size_t excess = words * LIMB_BITS - bits;
        if (excess > 0) {
            limb_t mask = ~limb_t(0) >> excess;
            n.data[words - 1] &= mask;
            n.data[words - 1] |= (limb_t(1) << ((bits - 1) % LIMB_BITS));
        }
        n.data[0] |= limb_t(1);
    }


    rsa::bigInt rsa::gcd(bigInt a, bigInt b) {
        while (!b.isZero()) {
            bigInt q(a.capacity), r(a.capacity);
            divide(a, b, q, r);
            a = b;
            b = r;
        }
        return a;
    }

    rsa::bigInt rsa::modInverse(const bigInt& a, const bigInt& m) {
        // Extended Euclidean Algorithm: find x such that a*x ≡ 1 (mod m)
        // Tracks sign separately since bigInt is unsigned.

        bigInt old_r = m;
        bigInt r = a;

        // |old_s| = 0 (positive), |s| = 1 (positive)
        bigInt old_s_abs(m.capacity + 1);
        old_s_abs.data[0] = 0; old_s_abs.used = 1;
        bool old_s_neg = false;

        bigInt s_abs(m.capacity + 1);
        s_abs.data[0] = 1; s_abs.used = 1;
        bool s_neg = false;

        while (!r.isZero()) {
            bigInt q, rem;
            divide(old_r, r, q, rem);

            old_r = r;
            r = rem;

            // temp = q * |s|
            bigInt temp(q.used + s_abs.used + 1);
            multiply(q, s_abs, temp);

            // new_s = old_s - q * s  (signed arithmetic)
            bigInt new_abs;
            bool new_neg;

            if (old_s_neg == s_neg) {
                // Same sign: subtract magnitudes
                int cmp = compare(old_s_abs, temp);
                if (cmp >= 0) {
                    new_abs = bigInt(old_s_abs.capacity);
                    subtract(old_s_abs, temp, new_abs);
                    new_neg = old_s_neg;
                } else {
                    new_abs = bigInt(temp.capacity);
                    subtract(temp, old_s_abs, new_abs);
                    new_neg = !old_s_neg;
                }
            } else {
                // Different signs: add magnitudes
                new_abs = bigInt(std::max(old_s_abs.capacity, temp.capacity) + 1);
                add(old_s_abs, temp, new_abs);
                new_neg = old_s_neg;
            }

            old_s_abs = s_abs;
            old_s_neg = s_neg;
            s_abs = new_abs;
            s_neg = new_neg;
        }

        // If result is negative, add m to make it positive
        if (old_s_neg) {
            bigInt result(m.capacity + 1);
            subtract(m, old_s_abs, result);
            return result;
        }
        return old_s_abs;
    }

    bool rsa::isProbablyPrime(const bigInt& n, int k) {
        bigInt one(1U, n.capacity);
        bigInt two(2U, n.capacity);
        bigInt three(3U, n.capacity);

        // nm1 = n - 1
        bigInt nm1(n.capacity);
        subtract(n, one, nm1);

        // trivial checks
        if (compare(n, one) <= 0) return false;
        if (n.isEven()) return compare(n, two) == 0;

        // ------------------------------------------------------------
        // Lambda: randomBigIntBits(out,bits)
        // ------------------------------------------------------------
        auto randomBigIntBits = [&](bigInt& out, size_t bits) {
            size_t words = (bits + LIMB_BITS - 1) / LIMB_BITS;
            out.reserve(words);
            out.used = words;

            // fill random words
            for (size_t i = 0; i < words; ++i) {
                fillRandomBytes(reinterpret_cast<uint8_t*>(&out.data[i]),
                                sizeof(limb_t));
            }

            // mask unused high bits
            size_t extra = words * LIMB_BITS - bits;
            if (extra > 0) {
                limb_t mask = ~limb_t(0) >> extra;
                out.data[words - 1] &= mask;
            }

            // optional normalize if you have it:
            // out.normalize();
        };

        // ------------------------------------------------------------
        // Lambda: randomBelow(limit)   -> uniform r in [0..limit-1]
        // ------------------------------------------------------------
        auto randomBelow = [&](const bigInt& limit) -> bigInt {
            size_t bits = limit.bitLength();
            bigInt r(limit.capacity);

            for (;;) {
                randomBigIntBits(r, bits);

                if (compare(r, limit) < 0)
                    return r;
            }
        };

        // ------------------------------------------------------------
        // Lambda: randomWitnessA(n) -> uniform a in [2..n-2]
        // ------------------------------------------------------------
        auto randomWitnessA = [&]() -> bigInt {

            // nMinusThree = n - 3
            bigInt nMinusThree(n.capacity);
            subtract(n, three, nMinusThree);   // n-3

            // r ∈ [0..n-4]
            bigInt r = randomBelow(nMinusThree);

            // a = r + 2  => [2..n-2]
            bigInt a(n.capacity);
            add(r, two, a);
            return a;
        };

        // ------------------------------------------------------------
        // factor n-1 = 2^s * d
        // ------------------------------------------------------------
        bigInt d = nm1;
        size_t s = 0;
        while (d.isEven()) {
            d.divideByTwo();
            s++;
        }

        // ------------------------------------------------------------
        // Miller-Rabin rounds
        // ------------------------------------------------------------
        for (int i = 0; i < k; i++) {

            bigInt a = randomWitnessA();

            bigInt x = modPow(a, d, n);

            // x == 1 or x == n-1
            if ((x.used == 1 && x.data[0] == 1) || compare(x, nm1) == 0)
                continue;

            bool composite = true;

            for (size_t r = 1; r < s; r++) {
                // x = x^2 mod n  (direct squaring, much faster than modPow)
                bigInt x2(x.used * 2 + 2);
                multiply(x, x, x2);
                bigInt q_sq(x2.used + 1), r_sq(n.capacity + 1);
                divide(x2, n, q_sq, r_sq);
                x = std::move(r_sq);

                if (compare(x, nm1) == 0) {
                    composite = false;
                    break;
                }
            }

            if (composite) return false;
        }

        return true;
    }

    void rsa::findPrime(bigInt& p, size_t bits) {
        bigInt two(2U, 1);

        // Small primes for trial division (skip obviously composite candidates)
        static const uint32_t smallPrimes[] = {
            3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,
            79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,
            163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,
            241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,
            337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,
            431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,
            521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,
            617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,
            719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,
            823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,
            929,937,941,947,953,967,971,977,983,991,997
        };
        static const size_t numSmallPrimes = sizeof(smallPrimes) / sizeof(smallPrimes[0]);

        // Lambda: check trial division by small primes
        auto passesTrialDivision = [&](const bigInt& candidate) -> bool {
            for (size_t i = 0; i < numSmallPrimes; ++i) {
                // Compute candidate mod smallPrimes[i] using dlimb_t arithmetic
                dlimb_t rem = 0;
                uint32_t sp = smallPrimes[i];
                for (size_t w = candidate.used; w-- > 0; ) {
                    rem = (rem << LIMB_BITS) | candidate.data[w];
                    rem %= sp;
                }
                if (rem == 0) return false;
            }
            return true;
        };

        // Initial random start (ensured odd by generateSecureRandom)
        generateSecureRandom(p, bits);

        // Search: skip composites with trial division, then confirm with Miller-Rabin
        for (;;) {
            if (passesTrialDivision(p) && isProbablyPrime(p, 10))
                return;
            // Add 2 in-place
            dlimb_t carry = 2;
            for (size_t i = 0; i < p.used && carry; ++i) {
                dlimb_t sum = (dlimb_t)p.data[i] + carry;
                p.data[i] = (limb_t)sum;
                carry = sum >> LIMB_BITS;
            }
            if (carry) {
                p.reserve(p.used + 1);
                p.data[p.used] = (limb_t)carry;
                p.used++;
            }
        }
    }

    void rsa::generateKeys(size_t bit_count) {
        // bit_count is the target size of N (e.g. 2048).
        // p and q should each be half of that so n = p*q has bit_count bits.
        size_t half_bits = bit_count / 2;
        bigInt p(half_bits / LIMB_BITS + 2), q(half_bits / LIMB_BITS + 2);

        findPrime(p, half_bits);
        findPrime(q, half_bits);

        // n = p * q
        n.reserve(p.used + q.used);
        multiply(p, q, n);

        // Constants for subtraction
        bigInt one(1U, 1);
        bigInt two(2U, 1);

        // phi = (p-1)(q-1)
        bigInt pm1(p.capacity), qm1(q.capacity), phi(n.capacity);
        subtract(p, one, pm1);
        subtract(q, one, qm1);
        multiply(pm1, qm1, phi);

        // Standard public exponent e = 65537
        // Using 65537U is more secure and faster than 17
        e = bigInt(65537U, 8);

        // Ensure e is coprime to phi
        while (true) {
            bigInt common = gcd(e, phi);
            // Check if gcd == 1
            if (common.used == 1 && common.data[0] == 1) {
                break;
            }
            bigInt next_e(e.capacity);
            add(e, two, next_e);
            e = next_e;
        }

        // Store primes for CRT key export
        this->p = p;
        this->q = q;

        // Calculate d = e^-1 mod phi
        this->d = modInverse(e, phi);

        // Optional: Verification logic without toString()
        /*
        bigInt check(n.capacity * 2);
        multiply(e, this->d, check);
        bigInt q_c(check.capacity), r_c(phi.capacity);
        divide(check, phi, q_c, r_c);
        if (r_c.used == 1 && r_c.data[0] == 1) {
            // Verification Success: (e*d % phi) == 1
        }
        */
    }

    void netplus::rsa::setRsaKeyFromRaw(
            const std::vector<uint8_t>& nBE,
            const std::vector<uint8_t>& eBE,
            const std::vector<uint8_t>& dBE
        ) {

            auto bigIntFromBytesBE = [&](const uint8_t* bytes, size_t len) -> rsa::bigInt {
                // Determine how many limb_t words we need
                constexpr size_t bytesPerLimb = LIMB_BITS / 8;
                size_t words = (len + bytesPerLimb - 1) / bytesPerLimb;
                rsa::bigInt x(words + 2);
                x.used = words;

                // Fill words from the end of the byte array
                for (size_t i = 0; i < len; ++i) {
                    size_t bytePos = len - 1 - i;
                    size_t wordIdx = i / bytesPerLimb;
                    size_t shift = (i % bytesPerLimb) * 8;
                    x.data[wordIdx] |= (static_cast<limb_t>(bytes[bytePos]) << shift);
                }
                return x;
            };

            n = bigIntFromBytesBE(nBE.data(), nBE.size());
            e = bigIntFromBytesBE(eBE.data(), eBE.size());
            d = bigIntFromBytesBE(dBE.data(), dBE.size());
        }

    rsa::bigInt rsa::encrypt(const bigInt& m) { return modPow(m, e, n); }
    rsa::bigInt rsa::decrypt(const bigInt& c) { return modPow(c, d, n); }

    // This is used to "clear" the lower bits during reduction
    limb_t rsa::calculateNPrime(limb_t n0) {
        limb_t inv = 1;
        for (int i = 0; i < LIMB_BITS; i++) {
            inv *= 2 - n0 * inv;
        }
        return -inv;
    }

    // 2. Calculate R mod N (where R = 2^(LIMB_BITS * mod.used))
    rsa::bigInt rsa::calculateRMod(const bigInt& mod) {
        bigInt r(mod.used * 2 + 1);
        r.fromUint64(1);

        // Shift 1 left by (LIMB_BITS * mod.used) bits, then mod N
        // This is equivalent to R mod N
        r.shiftLeft(mod.used * LIMB_BITS);

        bigInt q(r.used), remainder(mod.used);
        divide(r, mod, q, remainder);
        return remainder;
    }

    // 3. Montgomery Multiplication: (a * b * R^-1) mod N
    // This is the core "speed" function that avoids long division.
    rsa::bigInt rsa::montgomeryMultiply(const bigInt& a, const bigInt& b, const bigInt& mod, limb_t n_prime) {
        size_t n = mod.used;
        bigInt res(2 * n + 1);
        bigInt final_res(n + 1);

        montgomeryMultiply_into(a, b, mod, n_prime, res, final_res);
        return final_res;
    }

    // In-place Montgomery multiply using CIOS (Coarsely Integrated Operand Scanning).
    // Fuses multiplication and reduction into a single pass for better cache locality.
    // scratch is used as the n+2 word accumulator T.
    void rsa::montgomeryMultiply_into(const bigInt& a, const bigInt& b,
                                       const bigInt& mod, limb_t n_prime,
                                       bigInt& scratch, bigInt& out) {
        const size_t n = mod.used;
        const size_t tlen = n + 2;

        // Use scratch as T accumulator
        if (scratch.capacity < tlen) scratch.reserve(tlen);
        limb_t* __restrict__ T = scratch.data.get();
        std::memset(T, 0, tlen * sizeof(limb_t));

        const limb_t* __restrict__ ap = a.data.get();
        const limb_t* __restrict__ bp = b.data.get();
        const limb_t* __restrict__ mp = mod.data.get();
        const size_t au = a.used;
        const size_t bu = b.used;

        {
            for (size_t i = 0; i < n; ++i) {
                // Step 1: T += a[i] * b
                const dlimb_t ai = (i < au) ? (dlimb_t)ap[i] : 0;
                dlimb_t carry = 0;
                if (ai != 0) {
                    for (size_t j = 0; j < n; ++j) {
                        const dlimb_t bj = (j < bu) ? (dlimb_t)bp[j] : 0;
                        dlimb_t cur = (dlimb_t)T[j] + ai * bj + carry;
                        T[j] = (limb_t)cur;
                        carry = cur >> LIMB_BITS;
                    }
                }
                {
                    dlimb_t cur = (dlimb_t)T[n] + carry;
                    T[n] = (limb_t)cur;
                    T[n + 1] = (limb_t)(cur >> LIMB_BITS);
                }

                // Step 2: Montgomery reduction — T += m_i * mod, then shift right by one word
                const dlimb_t mi = (dlimb_t)((limb_t)(T[0] * n_prime));
                carry = 0;
                // First word: T[0] + mi*mod[0] is zero mod 2^LIMB_BITS, just capture carry
                {
                    dlimb_t cur = (dlimb_t)T[0] + mi * (dlimb_t)mp[0];
                    carry = cur >> LIMB_BITS;
                }
                for (size_t j = 1; j < n; ++j) {
                    dlimb_t cur = (dlimb_t)T[j] + mi * (dlimb_t)mp[j] + carry;
                    T[j - 1] = (limb_t)cur;
                    carry = cur >> LIMB_BITS;
                }
                {
                    dlimb_t cur = (dlimb_t)T[n] + carry;
                    T[n - 1] = (limb_t)cur;
                    carry = cur >> LIMB_BITS;
                }
                T[n] = T[n + 1] + (limb_t)carry;
                T[n + 1] = 0;
            }
        }

        // Result is T[0..n-1] (possibly n words)
        if (out.capacity < n + 1) out.reserve(n + 1);
        limb_t* __restrict__ op = out.data.get();
        std::memcpy(op, T, (n + 1) * sizeof(limb_t));
        out.used = (T[n] != 0) ? n + 1 : n;
        while (out.used > 1 && op[out.used - 1] == 0) out.used--;

        // Final step: if out >= mod, subtract mod in-place
        if (compare(out, mod) >= 0) {
            subtract(out, mod, scratch);
            out.used = scratch.used;
            std::memcpy(op, scratch.data.get(), scratch.used * sizeof(limb_t));
            if (out.capacity > out.used)
                std::memset(op + out.used, 0, (out.capacity - out.used) * sizeof(limb_t));
        }
    }

    rsa::bigInt rsa::bytesToBigIntBE(const std::vector<uint8_t>& bytes) {
        constexpr size_t bytesPerLimb = LIMB_BITS / 8;
        size_t words = (bytes.size() + bytesPerLimb - 1) / bytesPerLimb;
        netplus::rsa::bigInt out(std::max<size_t>(words + 1, 1));
        out.used = 1;
        out.data[0] = 0;

        for (uint8_t b : bytes) {
            // out = out << 8
            limb_t carry = 0;
            for (size_t i = 0; i < out.used; ++i) {
                limb_t newCarry = out.data[i] >> (LIMB_BITS - 8);
                out.data[i] = (out.data[i] << 8) | carry;
                carry = newCarry;
            }
            if (carry != 0) {
                out.data[out.used++] = carry;
            }

            // out = out + b
            dlimb_t sum = dlimb_t(out.data[0]) + b;
            out.data[0] = limb_t(sum);
            limb_t c = limb_t(sum >> LIMB_BITS);

            size_t idx = 1;
            while (c != 0 && idx < out.used) {
                dlimb_t s = dlimb_t(out.data[idx]) + c;
                out.data[idx] = limb_t(s);
                c = limb_t(s >> LIMB_BITS);
                ++idx;
            }
            if (c != 0) {
                out.data[out.used++] = c;
            }
        }

        // trim leading zero limbs (optional safety)
        while (out.used > 1 && out.data[out.used - 1] == 0) out.used--;

        return out;
    }

    rsa::bigInt rsa::bigIntFromBytesBE (const uint8_t* bytes, size_t len){
        constexpr size_t bytesPerLimb = LIMB_BITS / 8;
        size_t words = (len + bytesPerLimb - 1) / bytesPerLimb;
        rsa::bigInt x(words + 1);
        x.used = words;
        std::fill(x.data.get(), x.data.get() + x.capacity, limb_t(0));

        for (size_t i = 0; i < len; ++i) {
            uint8_t byte = bytes[len - 1 - i];     // LSB first
            size_t wordIdx = i / bytesPerLimb;
            size_t bitShift = (i % bytesPerLimb) * 8;
            x.data[wordIdx] |= (static_cast<limb_t>(byte) << bitShift);
        }
        while (x.used > 1 && x.data[x.used - 1] == 0) x.used--;
        return x;
    };

    std::vector<uint8_t> rsa::bigIntToBytesBE(const bigInt& x, size_t outLen){
        std::vector<uint8_t> out(outLen, 0x00);
        constexpr size_t bytesPerLimb = LIMB_BITS / 8;

        // Direct word extraction — O(n) instead of O(n²) shiftRight loop
        for (size_t i = 0; i < x.used && i * bytesPerLimb < outLen; ++i) {
            limb_t word = x.data[i];
            for (size_t b = 0; b < bytesPerLimb; ++b) {
                size_t bytePos = i * bytesPerLimb + b;
                if (bytePos < outLen) {
                    out[outLen - 1 - bytePos] = static_cast<uint8_t>(word & 0xFF);
                    word >>= 8;
                }
            }
        }

        return out;
    }

    // Static function to load RSA key from DER-encoded data


};
