Add baby-step giant-step discrete logarithm algorithm - #14997
Add baby-step giant-step discrete logarithm algorithm#14997felipeofdev-ai wants to merge 4 commits into
Conversation
Solves base^x ≡ target (mod modulus) in O(sqrt(modulus)) time with doctests for solutions, identity cases, and missing-logarithm errors.
for more information, see https://pre-commit.ci
|
Thanks for reviewing — CI is green on my side. Happy to adjust anything you need. — Felipe Fernandes · Systems & Agentic AI Engineer |
366652e to
979770d
Compare
|
@priya-sundaram-dev, please review. |
priya-sundaram-dev
left a comment
There was a problem hiding this comment.
Thanks for adding BSGS — the structure (baby-step table + giant-step factor via pow(base, -m, modulus)) is clean and readable. The doctests all pass. Unfortunately there's a correctness bug in the step count that makes the function report "no discrete logarithm" for many cases that do have a solution.
The bug
step_count = ceil(isqrt(modulus - 1))isqrt already returns an integer, so ceil(isqrt(x)) == isqrt(x) — the ceil is a no-op. That makes step_count = floor(sqrt(modulus - 1)). BSGS needs m = ceil(sqrt(n)) so that the giant/baby loops cover every exponent in 0 .. n-1 (the pair (giant, baby) reaches at most m*m - 1). When modulus - 1 isn't a perfect square, m*m - 1 < modulus - 2, so the largest exponents are never checked and a valid log is missed.
Reproducers (all have real solutions, all raise ValueError):
>>> baby_step_giant_step(2, 6, 11) # 2**9 % 11 == 6, should return 9
ValueError: no discrete logarithm for 6 base 2 modulo 11
>>> baby_step_giant_step(3, 4, 7) # 3**4 % 7 == 4, should return 4
>>> baby_step_giant_step(2, 7, 13) # 2**11 % 13 == 7, should return 11I brute-forced every (base, target) pair over the primes 5..97 and compared against this implementation: 524 pairs that have a solution incorrectly raise ValueError (0 wrong-but-nonzero answers — it never returns a wrong exponent, it just gives up early).
Fix — compute a true ceiling:
step_count = isqrt(modulus - 1) + 1With that one change the same exhaustive check over primes 5..97 passes with 0 failures. (isqrt(n-1)+1 is a safe ceil(sqrt(n)) for n >= 1; the at-most-one extra baby step is negligible.)
Could you also add a doctest that would have caught this, e.g.:
>>> baby_step_giant_step(2, 6, 11)
9Nice work overall — this is a one-line fix and then it's solid.
Describe your change:
Checklist:
Summary
Adds
maths/baby_step_giant_step.pyimplementing the classic baby-step giant-step method for discrete logarithms modulo a prime (or any modulus wherebaseis invertible).⌈√(modulus-1)⌉pow(base, -m, modulus)ValueErrorwhen no solution existspython -m doctestReference: https://en.wikipedia.org/wiki/Baby-step_giant-step
— Felipe Fernandes · Systems & Agentic AI Engineer
https://github.com/felipeofdev-ai · https://felipeofdev-ai.github.io/