diff --git a/cryptography/__init__.py b/cryptography/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cryptography/shor_algorithm.py b/cryptography/shor_algorithm.py new file mode 100644 index 000000000000..110d3278b40d --- /dev/null +++ b/cryptography/shor_algorithm.py @@ -0,0 +1,99 @@ +""" +Classical simulation of Shor's Algorithm to factor integers. + +Source: https://en.wikipedia.org/wiki/Shor%27s_algorithm +""" + +import math +import random +from typing import Any + + +def is_prime(n: int) -> bool: + """ + Check if a number is prime. + + >>> is_prime(2) + True + >>> is_prime(4) + False + """ + if n < 2: + return False + if n in (2, 3): + return True + if n % 2 == 0: + return False + r = math.isqrt(n) + return all(n % i != 0 for i in range(3, r + 1, 2)) + + +def modexp(a: int, b: int, m: int) -> int: + """ + Modular exponentiation: (a^b) % m + + >>> modexp(2, 5, 13) + 6 + """ + result = 1 + a = a % m + while b > 0: + if b & 1: + result = (result * a) % m + a = (a * a) % m + b >>= 1 + return result + + +def shor_classical(n: int, max_attempts: int = 10) -> str | tuple[int, int]: + """ + Classical approximation of Shor's Algorithm to factor a number. + + >>> result = shor_classical(15) + >>> isinstance(result, tuple) + True + >>> sorted(result) == [3, 5] + True + + >>> shor_classical(13) # Prime + 'No factors: 13 is prime' + """ + if n <= 1: + return "Failure: input must be > 1" + if n % 2 == 0: + return 2, n // 2 + if is_prime(n): + return f"No factors: {n} is prime" + + for _ in range(max_attempts): + a = random.randrange(2, n - 1) + g = math.gcd(a, n) + if g > 1: + return g, n // g + + r = 1 + while r < n: + if modexp(a, r, n) == 1: + break + r += 1 + else: + continue + + if r % 2 != 0: + continue + x = modexp(a, r // 2, n) + if x == n - 1: + continue + + factor1 = math.gcd(x - 1, n) + factor2 = math.gcd(x + 1, n) + if factor1 not in (1, n) and factor2 not in (1, n): + return factor1, factor2 + + return "Failure: try more attempts" + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/cryptography/tests/__init__.py b/cryptography/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cryptography/tests/test_shor_algorithm.py b/cryptography/tests/test_shor_algorithm.py new file mode 100644 index 000000000000..6c12e748a2a2 --- /dev/null +++ b/cryptography/tests/test_shor_algorithm.py @@ -0,0 +1,36 @@ +import pytest +from cryptography.shor_algorithm import shor_classical + + +def test_small_composite(): + factors = shor_classical(15) + assert set(factors) == {3, 5} + + +def test_medium_composite(): + factors = shor_classical(21) + assert set(factors) == {3, 7} + + +def test_even_number(): + factors = shor_classical(18) + assert set(factors) == {2, 9} + + +def test_prime_number(): + result = shor_classical(13) + assert isinstance(result, str) + assert "prime" in result.lower() + + +def test_invalid_input(): + result = shor_classical(1) + assert isinstance(result, str) + assert "failure" in result.lower() + + +def test_larger_composite_number(): + result = shor_classical(91) + assert isinstance(result, (tuple, str)) + if isinstance(result, tuple): + assert all(isinstance(x, int) for x in result)