From 5cdffffaec6b791d8b7583e1a4b85650df0dc6c0 Mon Sep 17 00:00:00 2001 From: Khushi Pal Date: Wed, 18 Jun 2025 21:40:17 +0530 Subject: [PATCH 1/3] Add classical implementation of Shor's Algorithm (#12318) --- cryptography/shor_algorithm.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 cryptography/shor_algorithm.py diff --git a/cryptography/shor_algorithm.py b/cryptography/shor_algorithm.py new file mode 100644 index 000000000000..e69de29bb2d1 From d37302382265732e2da77bab07a67124e3465226 Mon Sep 17 00:00:00 2001 From: Khushi Pal Date: Wed, 18 Jun 2025 22:13:58 +0530 Subject: [PATCH 2/3] =?UTF-8?q?Add=20classical=20Shor=E2=80=99s=20Algorith?= =?UTF-8?q?m=20implementation=20with=20tests=20(#12318)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cryptography/__init__.py | 0 cryptography/shor_algorithm.py | 102 ++++++++++++++++++++++ cryptography/tests/__init__.py | 0 cryptography/tests/test_shor_algorithm.py | 36 ++++++++ 4 files changed, 138 insertions(+) create mode 100644 cryptography/__init__.py create mode 100644 cryptography/tests/__init__.py create mode 100644 cryptography/tests/test_shor_algorithm.py 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 index e69de29bb2d1..4f070fe67ca1 100644 --- a/cryptography/shor_algorithm.py +++ b/cryptography/shor_algorithm.py @@ -0,0 +1,102 @@ +""" +Classical simulation of Shor's Algorithm to factor integers. + +Source: https://en.wikipedia.org/wiki/Shor%27s_algorithm +""" + +import random +import math +from typing import Tuple, Union + + +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 = int(math.isqrt(n)) + for i in range(3, r + 1, 2): + if n % i == 0: + return False + return True + + +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) -> Union[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) From c568a092f3532cb341fabac9197a3dc47966a084 Mon Sep 17 00:00:00 2001 From: Khushi Pal <114483555+khushipy@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:57:50 +0530 Subject: [PATCH 3/3] Update shor_algorithm.py --- cryptography/shor_algorithm.py | 43 ++++++++++++++++------------------ 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/cryptography/shor_algorithm.py b/cryptography/shor_algorithm.py index 4f070fe67ca1..110d3278b40d 100644 --- a/cryptography/shor_algorithm.py +++ b/cryptography/shor_algorithm.py @@ -4,9 +4,9 @@ Source: https://en.wikipedia.org/wiki/Shor%27s_algorithm """ -import random import math -from typing import Tuple, Union +import random +from typing import Any def is_prime(n: int) -> bool: @@ -24,11 +24,8 @@ def is_prime(n: int) -> bool: return True if n % 2 == 0: return False - r = int(math.isqrt(n)) - for i in range(3, r + 1, 2): - if n % i == 0: - return False - return True + 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: @@ -48,7 +45,7 @@ def modexp(a: int, b: int, m: int) -> int: return result -def shor_classical(N: int, max_attempts: int = 10) -> Union[str, Tuple[int, int]]: +def shor_classical(n: int, max_attempts: int = 10) -> str | tuple[int, int]: """ Classical approximation of Shor's Algorithm to factor a number. @@ -61,22 +58,22 @@ def shor_classical(N: int, max_attempts: int = 10) -> Union[str, Tuple[int, int] >>> shor_classical(13) # Prime 'No factors: 13 is prime' """ - if N <= 1: + 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" + 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) + a = random.randrange(2, n - 1) + g = math.gcd(a, n) if g > 1: - return g, N // g + return g, n // g r = 1 - while r < N: - if modexp(a, r, N) == 1: + while r < n: + if modexp(a, r, n) == 1: break r += 1 else: @@ -84,13 +81,13 @@ def shor_classical(N: int, max_attempts: int = 10) -> Union[str, Tuple[int, int] if r % 2 != 0: continue - x = modexp(a, r // 2, N) - if x == N - 1: + 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): + 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"