-
-
Couldn't load subscription status.
- 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 all commits
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
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,168 @@ | ||
| import math | ||
| import random | ||
|
|
||
|
|
||
| def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> int | None: | ||
| """ | ||
| Solve for x in the discrete logarithm problem: base^x ≡ target (mod modulus) | ||
| using Pollard's Rho algorithm. | ||
|
|
||
| This is a probabilistic algorithm that finds discrete logarithms in | ||
| O(√modulus) 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. | ||
|
|
||
| More info: https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm_for_logarithms | ||
|
|
||
| Parameters | ||
| ---------- | ||
| base : int | ||
| The generator (base of the exponential). | ||
| target : int | ||
| The target value (target ≡ base^x mod modulus). | ||
| modulus : int | ||
| A prime modulus. | ||
|
|
||
| Returns | ||
| ------- | ||
| int | None | ||
| 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 pseudo_random_function( | ||
| current_value: int, exponent_base: int, exponent_target: int | ||
| ) -> tuple[int, int, int]: | ||
| """ | ||
| Pseudo-random function that partitions the search space into 3 sets. | ||
|
|
||
| Returns a tuple of (new_value, new_exponent_base, new_exponent_target). | ||
| """ | ||
| if current_value % 3 == 0: | ||
| # Multiply by base | ||
| return ( | ||
| (current_value * base) % modulus, | ||
| (exponent_base + 1) % (modulus - 1), | ||
| exponent_target, | ||
| ) | ||
| elif current_value % 3 == 1: | ||
| # Square | ||
| return ( | ||
| (current_value * current_value) % modulus, | ||
| (2 * exponent_base) % (modulus - 1), | ||
| (2 * exponent_target) % (modulus - 1), | ||
| ) | ||
| else: | ||
| # Multiply by target | ||
| return ( | ||
| (current_value * target) % modulus, | ||
| exponent_base, | ||
| (exponent_target + 1) % (modulus - 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 | ||
| # current_value represents base^exponent_base * target^exponent_target | ||
| random.seed() # Ensure truly random values | ||
| exponent_base = random.randint(0, modulus - 2) | ||
| exponent_target = random.randint(0, modulus - 2) | ||
|
|
||
| # Ensure current_value = base^exponent_base * target^exponent_target mod modulus | ||
| current_value = ( | ||
| pow(base, exponent_base, modulus) * pow(target, exponent_target, modulus) | ||
| ) % modulus | ||
|
|
||
| # Skip if current_value is 0 or 1 (problematic starting points) | ||
| if current_value <= 1: | ||
| continue | ||
|
|
||
| # Tortoise and hare start at same position | ||
| tortoise_value, tortoise_exp_base, tortoise_exp_target = ( | ||
| current_value, | ||
| exponent_base, | ||
| exponent_target, | ||
| ) | ||
| hare_value, hare_exp_base, hare_exp_target = ( | ||
| current_value, | ||
| exponent_base, | ||
| exponent_target, | ||
| ) | ||
|
|
||
| # Increased iteration limit for better coverage | ||
| max_iterations = max(int(math.sqrt(modulus)) * 2, modulus // 2) | ||
| for i in range(1, max_iterations): | ||
| # Tortoise: one step | ||
| ( | ||
| tortoise_value, | ||
| tortoise_exp_base, | ||
| tortoise_exp_target, | ||
| ) = pseudo_random_function( | ||
| tortoise_value, tortoise_exp_base, tortoise_exp_target | ||
| ) | ||
| # Hare: two steps | ||
| hare_value, hare_exp_base, hare_exp_target = pseudo_random_function( | ||
| *pseudo_random_function(hare_value, hare_exp_base, hare_exp_target) | ||
| ) | ||
|
|
||
| if tortoise_value == hare_value and i > 1: # Avoid immediate collision | ||
| # Collision found | ||
| exponent_difference = (tortoise_exp_base - hare_exp_base) % ( | ||
| modulus - 1 | ||
| ) | ||
| target_difference = (hare_exp_target - tortoise_exp_target) % ( | ||
| modulus - 1 | ||
| ) | ||
|
|
||
| if target_difference == 0: | ||
| break # Try with different starting point | ||
|
|
||
| try: | ||
| # Compute modular inverse using extended Euclidean algorithm | ||
| inverse_target_diff = pow(target_difference, -1, modulus - 1) | ||
| except ValueError: | ||
| break # No inverse, try different starting point | ||
|
|
||
| discrete_log = (exponent_difference * inverse_target_diff) % ( | ||
| modulus - 1 | ||
| ) | ||
|
|
||
| # Verify the solution | ||
| if pow(base, discrete_log, modulus) == target: | ||
| return discrete_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,119 @@ | ||
| """ | ||
| 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 os | ||
| import sys | ||
| import unittest | ||
|
|
||
| # 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 | ||
| assert pow(2, result, 29) == 22 | ||
| found_solution = True | ||
| break | ||
|
|
||
| assert 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 | ||
| for _attempt in range(3): | ||
| result = pollards_rho_discrete_log(g, h, p) | ||
| if result is not None: | ||
| assert pow(g, result, p) == h | ||
| break | ||
| # Not all cases may have solutions, so we don't check for success | ||
|
|
||
| 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 | ||
| if (result := pollards_rho_discrete_log(3, 7, 11)) is not None: | ||
| # If it returns a result, it must be wrong since no solution exists | ||
| assert 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: | ||
| assert 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: | ||
| assert 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 | ||
| assert 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: | ||
| assert 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) | ||
| assert pow(g, result, p) == h | ||
|
|
||
| # Should find at least one solution in 10 attempts | ||
| assert 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not repeat the datatypes (int) from above, because if one is changed and the other is not, then readers become confused