Skip to content

Commit 1b909d7

Browse files
[pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
1 parent cba4cb8 commit 1b909d7

File tree

2 files changed

+66
-47
lines changed

2 files changed

+66
-47
lines changed

maths/pollard_rho_discrete_log.py

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> Optional[
99
using Pollard's Rho algorithm.
1010
1111
This is a probabilistic algorithm that finds discrete logarithms in O(√modulus) time.
12-
The algorithm may not always find a solution in a single run due to its
12+
The algorithm may not always find a solution in a single run due to its
1313
probabilistic nature, but it will find the correct answer when it succeeds.
14-
14+
1515
More info: https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm_for_logarithms
1616
1717
Parameters
@@ -33,15 +33,15 @@ def pollards_rho_discrete_log(base: int, target: int, modulus: int) -> Optional[
3333
>>> result = pollards_rho_discrete_log(2, 22, 29)
3434
>>> result is not None and pow(2, result, 29) == 22
3535
True
36-
36+
3737
>>> result = pollards_rho_discrete_log(3, 9, 11)
3838
>>> result is not None and pow(3, result, 11) == 9
3939
True
40-
40+
4141
>>> result = pollards_rho_discrete_log(5, 3, 7)
4242
>>> result is not None and pow(5, result, 7) == 3
4343
True
44-
44+
4545
>>> # Case with no solution should return None or fail verification
4646
>>> result = pollards_rho_discrete_log(3, 7, 11)
4747
>>> result is None or pow(3, result, 11) != 7
@@ -53,7 +53,7 @@ def pseudo_random_function(
5353
) -> tuple[int, int, int]:
5454
"""
5555
Pseudo-random function that partitions the search space into 3 sets.
56-
56+
5757
Returns a tuple of (new_value, new_exponent_base, new_exponent_target).
5858
"""
5959
if current_value % 3 == 0:
@@ -80,31 +80,43 @@ def pseudo_random_function(
8080

8181
# Try multiple random starting points to avoid immediate collisions
8282
max_attempts = 50 # Increased attempts for better reliability
83-
83+
8484
for attempt in range(max_attempts):
8585
# Use different starting values to avoid trivial collisions
8686
# current_value represents base^exponent_base * target^exponent_target
8787
random.seed() # Ensure truly random values
8888
exponent_base = random.randint(0, modulus - 2)
8989
exponent_target = random.randint(0, modulus - 2)
90-
90+
9191
# Ensure current_value = base^exponent_base * target^exponent_target mod modulus
92-
current_value = (pow(base, exponent_base, modulus) * pow(target, exponent_target, modulus)) % modulus
93-
92+
current_value = (
93+
pow(base, exponent_base, modulus) * pow(target, exponent_target, modulus)
94+
) % modulus
95+
9496
# Skip if current_value is 0 or 1 (problematic starting points)
9597
if current_value <= 1:
9698
continue
97-
99+
98100
# Tortoise and hare start at same position
99-
tortoise_value, tortoise_exp_base, tortoise_exp_target = current_value, exponent_base, exponent_target
100-
hare_value, hare_exp_base, hare_exp_target = current_value, exponent_base, exponent_target
101+
tortoise_value, tortoise_exp_base, tortoise_exp_target = (
102+
current_value,
103+
exponent_base,
104+
exponent_target,
105+
)
106+
hare_value, hare_exp_base, hare_exp_target = (
107+
current_value,
108+
exponent_base,
109+
exponent_target,
110+
)
101111

102112
# Increased iteration limit for better coverage
103113
max_iterations = max(int(math.sqrt(modulus)) * 2, modulus // 2)
104114
for i in range(1, max_iterations):
105115
# Tortoise: one step
106-
tortoise_value, tortoise_exp_base, tortoise_exp_target = pseudo_random_function(
107-
tortoise_value, tortoise_exp_base, tortoise_exp_target
116+
tortoise_value, tortoise_exp_base, tortoise_exp_target = (
117+
pseudo_random_function(
118+
tortoise_value, tortoise_exp_base, tortoise_exp_target
119+
)
108120
)
109121
# Hare: two steps
110122
hare_value, hare_exp_base, hare_exp_target = pseudo_random_function(
@@ -113,8 +125,12 @@ def pseudo_random_function(
113125

114126
if tortoise_value == hare_value and i > 1: # Avoid immediate collision
115127
# Collision found
116-
exponent_difference = (tortoise_exp_base - hare_exp_base) % (modulus - 1)
117-
target_difference = (hare_exp_target - tortoise_exp_target) % (modulus - 1)
128+
exponent_difference = (tortoise_exp_base - hare_exp_base) % (
129+
modulus - 1
130+
)
131+
target_difference = (hare_exp_target - tortoise_exp_target) % (
132+
modulus - 1
133+
)
118134

119135
if target_difference == 0:
120136
break # Try with different starting point
@@ -125,22 +141,24 @@ def pseudo_random_function(
125141
except ValueError:
126142
break # No inverse, try different starting point
127143

128-
discrete_log = (exponent_difference * inverse_target_diff) % (modulus - 1)
129-
144+
discrete_log = (exponent_difference * inverse_target_diff) % (
145+
modulus - 1
146+
)
147+
130148
# Verify the solution
131149
if pow(base, discrete_log, modulus) == target:
132150
return discrete_log
133151
break # This attempt failed, try with different starting point
134-
152+
135153
return None
136154

137155

138156
if __name__ == "__main__":
139157
import doctest
140-
158+
141159
# Run doctests
142160
doctest.testmod(verbose=True)
143-
161+
144162
# Also run the main example
145163
result = pollards_rho_discrete_log(2, 22, 29)
146164
print(f"pollards_rho_discrete_log(2, 22, 29) = {result}")

maths/test_pollard_rho_discrete_log.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,19 @@ def test_basic_example(self):
2929
self.assertEqual(pow(2, result, 29), 22)
3030
found_solution = True
3131
break
32-
33-
self.assertTrue(found_solution,
34-
"Algorithm should find a solution within 5 attempts")
32+
33+
self.assertTrue(
34+
found_solution, "Algorithm should find a solution within 5 attempts"
35+
)
3536

3637
def test_simple_cases(self):
3738
"""Test simple discrete log cases with known answers."""
3839
test_cases = [
39-
(2, 8, 17), # 2^3 ≡ 8 (mod 17)
40-
(5, 3, 7), # 5^5 ≡ 3 (mod 7)
41-
(3, 9, 11), # 3^2 ≡ 9 (mod 11)
40+
(2, 8, 17), # 2^3 ≡ 8 (mod 17)
41+
(5, 3, 7), # 5^5 ≡ 3 (mod 7)
42+
(3, 9, 11), # 3^2 ≡ 9 (mod 11)
4243
]
43-
44+
4445
for g, h, p in test_cases:
4546
# Try multiple times due to probabilistic nature
4647
found_solution = False
@@ -56,8 +57,7 @@ def test_no_solution_case(self):
5657
"""Test case where no solution exists."""
5758
# 3^x ≡ 7 (mod 11) has no solution (verified by brute force)
5859
# The algorithm should return None or fail to find a solution
59-
result = pollards_rho_discrete_log(3, 7, 11)
60-
if result is not None:
60+
if (result := pollards_rho_discrete_log(3, 7, 11)) is not None:
6161
# If it returns a result, it must be wrong since no solution exists
6262
self.assertNotEqual(pow(3, result, 11), 7)
6363

@@ -67,7 +67,7 @@ def test_edge_cases(self):
6767
result = pollards_rho_discrete_log(1, 1, 7)
6868
if result is not None:
6969
self.assertEqual(pow(1, result, 7), 1)
70-
70+
7171
# h = 1: g^x ≡ 1 (mod p) - looking for the multiplicative order
7272
result = pollards_rho_discrete_log(3, 1, 7)
7373
if result is not None:
@@ -76,27 +76,27 @@ def test_edge_cases(self):
7676
def test_small_primes(self):
7777
"""Test with small prime moduli."""
7878
test_cases = [
79-
(2, 4, 5), # 2^2 ≡ 4 (mod 5)
80-
(2, 3, 5), # 2^? ≡ 3 (mod 5)
81-
(2, 1, 3), # 2^2 ≡ 1 (mod 3)
82-
(3, 2, 5), # 3^3 ≡ 2 (mod 5)
79+
(2, 4, 5), # 2^2 ≡ 4 (mod 5)
80+
(2, 3, 5), # 2^? ≡ 3 (mod 5)
81+
(2, 1, 3), # 2^2 ≡ 1 (mod 3)
82+
(3, 2, 5), # 3^3 ≡ 2 (mod 5)
8383
]
84-
84+
8585
for g, h, p in test_cases:
8686
result = pollards_rho_discrete_log(g, h, p)
8787
if result is not None:
8888
# Verify the result is mathematically correct
8989
self.assertEqual(pow(g, result, p), h)
90-
90+
9191
def test_larger_examples(self):
9292
"""Test with larger numbers to ensure algorithm scales."""
9393
# Test cases with larger primes
9494
test_cases = [
95-
(2, 15, 31), # Find x where 2^x ≡ 15 (mod 31)
96-
(3, 10, 37), # Find x where 3^x ≡ 10 (mod 37)
97-
(5, 17, 41), # Find x where 5^x ≡ 17 (mod 41)
95+
(2, 15, 31), # Find x where 2^x ≡ 15 (mod 31)
96+
(3, 10, 37), # Find x where 3^x ≡ 10 (mod 37)
97+
(5, 17, 41), # Find x where 5^x ≡ 17 (mod 41)
9898
]
99-
99+
100100
for g, h, p in test_cases:
101101
result = pollards_rho_discrete_log(g, h, p)
102102
if result is not None:
@@ -108,17 +108,18 @@ def test_multiple_runs_consistency(self):
108108
# and ensure any returned result is mathematically correct
109109
g, h, p = 2, 22, 29
110110
results = []
111-
111+
112112
for _ in range(10): # Run 10 times
113113
result = pollards_rho_discrete_log(g, h, p)
114114
if result is not None:
115115
results.append(result)
116116
self.assertEqual(pow(g, result, p), h)
117-
117+
118118
# Should find at least one solution in 10 attempts
119-
self.assertGreater(len(results), 0,
120-
"Algorithm should find solution in multiple attempts")
119+
self.assertGreater(
120+
len(results), 0, "Algorithm should find solution in multiple attempts"
121+
)
121122

122123

123124
if __name__ == "__main__":
124-
unittest.main(verbosity=2)
125+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)