-
-
Notifications
You must be signed in to change notification settings - Fork 49k
Add Pollard’s Rho algorithm for discrete logarithm #13657
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
sonianuj287
wants to merge
6
commits into
TheAlgorithms:master
from
sonianuj287:pollard_rho_discrete_log
+287
−0
Closed
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
22bb965
Add Pollard’s Rho algorithm for discrete logarithm
sonianuj287 6fda4d5
Add Wikipedia reference and remove unused imports
sonianuj287 b31cc12
Improve code readability with descriptive names and type hints
sonianuj287 b241790
Fix all ruff linting issues
sonianuj287 01f9a98
Merge branch 'master' into pollard_rho_discrete_log
sonianuj287 13fcdd2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| from typing import Optional, Tuple | ||
|
Check failure on line 1 in maths/pollard_rho_discrete_log.py
|
||
| import math | ||
| import random | ||
|
|
||
|
|
||
| def pollards_rho_discrete_log(g: int, h: int, p: int) -> Optional[int]: | ||
| """ | ||
| Solve for x in the discrete logarithm problem: g^x ≡ h (mod p) | ||
| using Pollard's Rho algorithm. | ||
|
|
||
| This is a probabilistic algorithm that finds discrete logarithms in O(√p) time. | ||
| The algorithm may not always find a solution in a single run due to its | ||
| probabilistic nature, but it will find the correct answer when it succeeds. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| g : int | ||
| The generator (base). | ||
| h : int | ||
| The result value (h ≡ g^x mod p). | ||
| p : int | ||
| A prime modulus. | ||
|
|
||
| Returns | ||
| ------- | ||
| Optional[int] | ||
| The discrete log x if found, otherwise None. | ||
|
|
||
| Examples | ||
| -------- | ||
| >>> result = pollards_rho_discrete_log(2, 22, 29) | ||
| >>> result is not None and pow(2, result, 29) == 22 | ||
| True | ||
|
|
||
| >>> result = pollards_rho_discrete_log(3, 9, 11) | ||
| >>> result is not None and pow(3, result, 11) == 9 | ||
| True | ||
|
|
||
| >>> result = pollards_rho_discrete_log(5, 3, 7) | ||
| >>> result is not None and pow(5, result, 7) == 3 | ||
| True | ||
|
|
||
| >>> # Case with no solution should return None or fail verification | ||
| >>> result = pollards_rho_discrete_log(3, 7, 11) | ||
| >>> result is None or pow(3, result, 11) != 7 | ||
| True | ||
| """ | ||
|
|
||
| def f(x, a, b): | ||
sonianuj287 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """Pseudo-random function that partitions the search space into 3 sets.""" | ||
| if x % 3 == 0: | ||
| # Multiply by g | ||
| return (x * g) % p, (a + 1) % (p - 1), b | ||
| elif x % 3 == 1: | ||
| # Square | ||
| return (x * x) % p, (2 * a) % (p - 1), (2 * b) % (p - 1) | ||
| else: | ||
| # Multiply by h | ||
| return (x * h) % p, a, (b + 1) % (p - 1) | ||
|
|
||
| # Try multiple random starting points to avoid immediate collisions | ||
| max_attempts = 50 # Increased attempts for better reliability | ||
|
|
||
| for attempt in range(max_attempts): | ||
| # Use different starting values to avoid trivial collisions | ||
| # x represents g^a * h^b | ||
| random.seed() # Ensure truly random values | ||
| a = random.randint(0, p - 2) | ||
| b = random.randint(0, p - 2) | ||
|
|
||
| # Ensure x = g^a * h^b mod p | ||
| x = (pow(g, a, p) * pow(h, b, p)) % p | ||
|
|
||
| # Skip if x is 0 or 1 (problematic starting points) | ||
| if x <= 1: | ||
| continue | ||
|
|
||
| X, A, B = x, a, b # Tortoise and hare start at same position | ||
|
|
||
| # Increased iteration limit for better coverage | ||
| max_iterations = max(int(math.sqrt(p)) * 2, p // 2) | ||
| for i in range(1, max_iterations): | ||
| # Tortoise: one step | ||
| x, a, b = f(x, a, b) | ||
| # Hare: two steps | ||
| X, A, B = f(*f(X, A, B)) | ||
|
|
||
| if x == X and i > 1: # Avoid immediate collision | ||
| # Collision found | ||
| r = (a - A) % (p - 1) | ||
| s = (B - b) % (p - 1) | ||
|
|
||
| if s == 0: | ||
| break # Try with different starting point | ||
|
|
||
| try: | ||
| # Compute modular inverse using extended Euclidean algorithm | ||
| inv_s = pow(s, -1, p - 1) | ||
| except ValueError: | ||
| break # No inverse, try different starting point | ||
|
|
||
| x_log = (r * inv_s) % (p - 1) | ||
|
|
||
| # Verify the solution | ||
| if pow(g, x_log, p) == h: | ||
| return x_log | ||
| break # This attempt failed, try with different starting point | ||
|
|
||
| return None | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| import doctest | ||
|
|
||
| # Run doctests | ||
| doctest.testmod(verbose=True) | ||
|
|
||
| # Also run the main example | ||
| result = pollards_rho_discrete_log(2, 22, 29) | ||
| print(f"pollards_rho_discrete_log(2, 22, 29) = {result}") | ||
| if result is not None: | ||
| print(f"Verification: 2^{result} mod 29 = {pow(2, result, 29)}") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| """ | ||
| Test suite for Pollard's Rho Discrete Logarithm Algorithm. | ||
|
|
||
| This module contains comprehensive tests for the pollard_rho_discrete_log module, | ||
| including basic functionality tests, edge cases, and performance validation. | ||
| """ | ||
|
|
||
| import unittest | ||
| import sys | ||
| import os | ||
|
|
||
| # Add the parent directory to sys.path to import maths module | ||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||
|
|
||
| from maths.pollard_rho_discrete_log import pollards_rho_discrete_log | ||
|
|
||
|
|
||
| class TestPollardRhoDiscreteLog(unittest.TestCase): | ||
| """Test cases for Pollard's Rho Discrete Logarithm Algorithm.""" | ||
|
|
||
| def test_basic_example(self): | ||
| """Test the basic example from the GitHub issue.""" | ||
| # Since the algorithm is probabilistic, try multiple times | ||
| found_solution = False | ||
| for attempt in range(5): # Try up to 5 times | ||
| result = pollards_rho_discrete_log(2, 22, 29) | ||
| if result is not None: | ||
| # Verify the result is correct | ||
| self.assertEqual(pow(2, result, 29), 22) | ||
| found_solution = True | ||
| break | ||
|
|
||
| self.assertTrue(found_solution, | ||
| "Algorithm should find a solution within 5 attempts") | ||
|
|
||
| def test_simple_cases(self): | ||
| """Test simple discrete log cases with known answers.""" | ||
| test_cases = [ | ||
| (2, 8, 17), # 2^3 ≡ 8 (mod 17) | ||
| (5, 3, 7), # 5^5 ≡ 3 (mod 7) | ||
| (3, 9, 11), # 3^2 ≡ 9 (mod 11) | ||
| ] | ||
|
|
||
| for g, h, p in test_cases: | ||
| # Try multiple times due to probabilistic nature | ||
| found_solution = False | ||
| for attempt in range(3): | ||
| result = pollards_rho_discrete_log(g, h, p) | ||
| if result is not None: | ||
| self.assertEqual(pow(g, result, p), h) | ||
| found_solution = True | ||
| break | ||
| # Not all cases may have solutions, so we don't assert found_solution | ||
|
|
||
| def test_no_solution_case(self): | ||
| """Test case where no solution exists.""" | ||
| # 3^x ≡ 7 (mod 11) has no solution (verified by brute force) | ||
| # The algorithm should return None or fail to find a solution | ||
| result = pollards_rho_discrete_log(3, 7, 11) | ||
| if result is not None: | ||
| # If it returns a result, it must be wrong since no solution exists | ||
| self.assertNotEqual(pow(3, result, 11), 7) | ||
|
|
||
| def test_edge_cases(self): | ||
| """Test edge cases and input validation scenarios.""" | ||
| # g = 1: 1^x ≡ h (mod p) only has solution if h = 1 | ||
| result = pollards_rho_discrete_log(1, 1, 7) | ||
| if result is not None: | ||
| self.assertEqual(pow(1, result, 7), 1) | ||
|
|
||
| # h = 1: g^x ≡ 1 (mod p) - looking for the multiplicative order | ||
| result = pollards_rho_discrete_log(3, 1, 7) | ||
| if result is not None: | ||
| self.assertEqual(pow(3, result, 7), 1) | ||
|
|
||
| def test_small_primes(self): | ||
| """Test with small prime moduli.""" | ||
| test_cases = [ | ||
| (2, 4, 5), # 2^2 ≡ 4 (mod 5) | ||
| (2, 3, 5), # 2^? ≡ 3 (mod 5) | ||
| (2, 1, 3), # 2^2 ≡ 1 (mod 3) | ||
| (3, 2, 5), # 3^3 ≡ 2 (mod 5) | ||
| ] | ||
|
|
||
| for g, h, p in test_cases: | ||
| result = pollards_rho_discrete_log(g, h, p) | ||
| if result is not None: | ||
| # Verify the result is mathematically correct | ||
| self.assertEqual(pow(g, result, p), h) | ||
|
|
||
| def test_larger_examples(self): | ||
| """Test with larger numbers to ensure algorithm scales.""" | ||
| # Test cases with larger primes | ||
| test_cases = [ | ||
| (2, 15, 31), # Find x where 2^x ≡ 15 (mod 31) | ||
| (3, 10, 37), # Find x where 3^x ≡ 10 (mod 37) | ||
| (5, 17, 41), # Find x where 5^x ≡ 17 (mod 41) | ||
| ] | ||
|
|
||
| for g, h, p in test_cases: | ||
| result = pollards_rho_discrete_log(g, h, p) | ||
| if result is not None: | ||
| self.assertEqual(pow(g, result, p), h) | ||
|
|
||
| def test_multiple_runs_consistency(self): | ||
| """Test that multiple runs give consistent results.""" | ||
| # Since the algorithm is probabilistic, run it multiple times | ||
| # and ensure any returned result is mathematically correct | ||
| g, h, p = 2, 22, 29 | ||
| results = [] | ||
|
|
||
| for _ in range(10): # Run 10 times | ||
| result = pollards_rho_discrete_log(g, h, p) | ||
| if result is not None: | ||
| results.append(result) | ||
| self.assertEqual(pow(g, result, p), h) | ||
|
|
||
| # Should find at least one solution in 10 attempts | ||
| self.assertGreater(len(results), 0, | ||
| "Algorithm should find solution in multiple attempts") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main(verbosity=2) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.