Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions maths/pollard_rho_discrete_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
Pollard's Rho Algorithm for Discrete Logarithm

Solves the discrete logarithm problem: Find x such that g^x ≡ h (mod p), where g is a generator, h is the target,

Check failure on line 4 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/pollard_rho_discrete_log.py:4:89: E501 Line too long (113 > 88)
and p is prime. Uses Pollard's Rho method with random walks and Brent's cycle detection.

Examples:
Example:
pollards_rho_discrete_log(2, 22, 29)
11
(Since 2^11 % 29 = 22)

Constraints:
- p should be prime, 3 <= p <= 10^6 (for reasonable runtime).
- g should be a primitive root modulo p (or at least a generator).
- Algorithm is probabilistic; may require multiple runs for success.

Implementation: Random walks in group with collision detection.
Time Complexity: O(√p) expected
Space Complexity: O(1)
"""

import random
from typing import Optional


def pollards_rho_discrete_log(g: int, h: int, p: int) -> Optional[int]:

Check failure on line 27 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (UP045)

maths/pollard_rho_discrete_log.py:27:58: UP045 Use `X | None` for type annotations

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As there is no test file in this pull request nor any test function or class in the file maths/pollard_rho_discrete_log.py, please provide doctest for the function pollards_rho_discrete_log

Please provide descriptive name for the parameter: g

Please provide descriptive name for the parameter: h

Please provide descriptive name for the parameter: p

"""
Returns x such that g^x ≡ h (mod p), using Pollard's Rho discrete logarithm algorithm.

Check failure on line 29 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/pollard_rho_discrete_log.py:29:89: E501 Line too long (90 > 88)

Args:
g (int): Base (generator).
h (int): Target value.
p (int): Modulus (prime).

Returns:
int or None: The discrete logarithm x if found, else None (retry with different parameters).

Check failure on line 37 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/pollard_rho_discrete_log.py:37:89: E501 Line too long (100 > 88)
"""
if p < 3 or h == 0:
raise ValueError("Invalid input: p must be prime >= 3, h != 0")

# Function to compute discrete log candidate using baby-step giant-step on subgroup if needed

Check failure on line 42 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/pollard_rho_discrete_log.py:42:89: E501 Line too long (97 > 88)
# But for full Rho, we use tortoise-hare
def f(x: int, y: int) -> int:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide descriptive name for the function: f

As there is no test file in this pull request nor any test function or class in the file maths/pollard_rho_discrete_log.py, please provide doctest for the function f

Please provide descriptive name for the parameter: x

Please provide descriptive name for the parameter: y

"""Random walk function: x * g^y mod p (simplified Brent variant)."""
return (x * pow(g, y, p)) % p

# Initial values (randomize for retries)
x0 = random.randint(1, p - 1)
y0 = random.randint(0, p - 1)
tortoise = (x0, y0)
hare = (x0, y0)
mu = 0 # Cycle start

# Brent's cycle detection
power = 1
lam = 1
while True:
# Move tortoise one step
tortoise = (f(tortoise[0], tortoise[1]), tortoise[1] + 1)
if tortoise[0] == h:
return tortoise[1] % (p - 1) # x mod phi(p)

# Move hare 2^power steps
for _ in range(power):
hare = (f(hare[0], hare[1]), hare[1] + 1)
if hare[0] == h:
return hare[1] % (p - 1)

if tortoise[0] == hare[0]:
# Collision detected
if tortoise[1] == hare[1]:
# Same point, restart
x0 = random.randint(1, p - 1)
y0 = random.randint(0, p - 1)
tortoise = (x0, y0)
hare = (x0, y0)
continue

# Compute mu and lam for cycle
mu = 0
tortoise2 = (x0, y0)
while tortoise2 != tortoise:
tortoise2 = (f(tortoise2[0], tortoise2[1]), tortoise2[1] + 1)
mu += 1

lam = 1
hare2 = tortoise
while hare2 != tortoise:
hare2 = (f(hare2[0], hare2[1]), hare2[1] + 1)
tortoise = (f(tortoise[0], tortoise[1]), tortoise[1] + 1)
lam += 1

# Now solve for x using collision
# Since collision at x * g^y = h, but for DLP, we need to adjust
# This is simplified; for full DLP, use baby-step on subgroup or BSGS hybrid
# For this implementation, assume collision gives candidate x = (hare[1] - tortoise[1]) mod (p-1)

Check failure on line 97 in maths/pollard_rho_discrete_log.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E501)

maths/pollard_rho_discrete_log.py:97:89: E501 Line too long (109 > 88)
candidate_x = (hare[1] - tortoise[1]) % (p - 1)
if pow(g, candidate_x, p) == h:
return candidate_x

# If not, retry with new random
x0 = random.randint(1, p - 1)
y0 = random.randint(0, p - 1)
tortoise = (x0, y0)
hare = (x0, y0)

power *= 2
if power > p: # Prevent infinite loop
return None

return None # Fallback


# Optional: Simple tests
if __name__ == "__main__":
# Test with example: 2^11 % 29 = 22
result = pollards_rho_discrete_log(2, 22, 29)
assert result == 11, f"Expected 11, got {result}"
print(f"Test passed: {result}")

# Another test: 5^3 % 13 = 12 (5^3 = 125 % 13 = 12)
result2 = pollards_rho_discrete_log(5, 12, 13)
assert result2 == 3, f"Expected 3, got {result2}"
print(f"Test passed: {result2}")
Loading