import random


def miller_rabin(n, k=5):
    if n == 2 or n == 3:
        return True
    if n <= 1 or n % 2 == 0:
        return False

    # Write n-1 as d * 2^r
    r = 0
    d = n - 1
    while d % 2 == 0:
        d //= 2
        r += 1

    # Perform the test k times
    for _ in range(k):
        a = random.randint(2, n - 2)
        x = pow(a, d, n)  # x = a^d % n
        if x == 1 or x == n - 1:
            continue

        # Keep squaring x and check for n-1
        for _ in range(r - 1):
            x = pow(x, 2, n)
            if x == n - 1:
                break
        else:
            return False

    return True


assert miller_rabin(0) == False
assert miller_rabin(1) == False
assert miller_rabin(2) == True
assert miller_rabin(3) == True
assert miller_rabin(4) == False


def primitive_isprime(num):
    for n in range(2, int(num**0.5) + 1):
        if num % n == 0:
            return False
    return True


assert not miller_rabin(8)
assert miller_rabin(11)


def mod_inverse(a, m):
    return pow(a, -1, m)


assert mod_inverse(3, 7) == 5
assert mod_inverse(10, 17) == 12
assert mod_inverse(7, 13) == 2
assert mod_inverse(65537, 3445361432) is not None


def gcd(x, y):
    while y:
        x, y = y, x % y
    return abs(x)


assert gcd(3, 9) == 3
assert gcd(3, 4) == 1

p = 60737
q = 56713
assert miller_rabin(p)
assert miller_rabin(q)

n = p * q
phi_n = (p - 1) * (q - 1)
assert not miller_rabin(n)
assert not miller_rabin(phi_n)

e = 65537
assert e == (1 << 16) | 0x1
assert gcd(e, phi_n) == 1

d = mod_inverse(e, phi_n)
assert d is not None and (e * d) % phi_n == 1

m = 69

c = pow(69, e, n)
dec = pow(c, d, n)

print("Ciphertext: ", c)
print("Decrypted: ", dec)

assert dec == m