Pollard Rho and Pollard Kangaroo for Dormant Bitcoin Wallets
Coding Pollard Kangaroo and Pollard Rho Algorithms for Discrete Logarithms
Quick intro
LeetArxiv is Leetcode for implementing Arxiv and other research papers.

This is part of our series on Practical Index Calculus for Computer Programmers.
Part 1: Discrete Logarithms and the Index Calculus Solution.
Part 2: Solving Pell Equations with Index Calculus and Algebraic Numbers.
Part 3: Solving Index Calculus Equations over Integers and Finite Fields.
Part 4 (we are here): Pollard Kangaroo when Index Calculus Fails.
Part 5 : Smart Attack on Anomalous Curves.
Part 6: Hacking Dormant Bitcoin Wallets in C.
Feel free to email me for sponsorships: murage.kibicho@leetarxiv.com
1.0 Paper Introduction
John Pollard’s 1978 paper, Monte Carlo Methods for Index Computation (mod p), (Pollard, 1978)1 introduced the Pollard Kangaroo algorithm as well as the Pollard Rho algorithm for solving discrete logarithm problems.
Pollard’s paper demonstrated statistical alternatives to index calculus for discrete logarithm problems.
Motivation and Summary
Passwords, bitcoin wallets and modern cryptography rely on the hardness of the discrete logarithm problem. Pollard Rho and Pollard Kangaroo are algorithms designed to ease the burden.
Pollard Rho
Works on all finite fields, integers and even on elliptic curves.
Always guarantees finding a solution.
Reduces the search space by a half.
Pollard Kangaroo
Used when you know the search range, like in bitcoin puzzles.
One is not guaranteed to find a solution.
We wrote this guide to supplement our previous article:
1.1 Introduction to Discrete Logarithm Problem
This section introduces two different discrete logarithm problems Pollard’s algorithms work within.
Integer Discrete Logarithm Problem
The Integer Discrete Logarithm Problem is the challenge of finding an integer x satisfying (Howell, 1998)2:
Elliptic Curve Discrete Logarithm Problem (ECDLP)
The Elliptic Curve Discrete Logarithm Problem (ECDLP) is the challenge of finding an integer m satisfying (Silverman, 2007)3:
Pollard Rho and Pollard kangaroo are useful solutions to these problems when index calculus fails.
2.0 Pollard’s Algorithms



This section covers coding implementations of the two different algorithms to solve discrete logarithm problems introduced in (Pollard, 1978): Pollard’s Rho, and Pollard’s Kangaroo.
2.1 Rho Method for Index Computation
Pollard’s Rho algorithm works by finding collisions in the cycle of a finite field. The rho algorithm utilises Floyd’s cycle finding algorithm (Knuth, 1969)4 to detect discrete logarithm collisions.
Imagine you are trying to solve ax ≡ B mod (p).
The rho algorithm is split into three steps (Menezes et. al, 2001)5:
Partition the elements of the set and create iteration rules.
The set is split into three partitions using the modulo operator.
Use iteration functions.
x mod 3 ≡ 0 : square the element.
x mod 3 ≡ 1 : multiply the element by the generator.
x mod 3 ≡ 2 : multiply the element by the desired value.
Find cycles using Floyd’s algorithm (Algorithms, 2025)6.
This is also called the tortoise and hair algorithm.
Take two pointers, a slow pointer that moves one step at a time and a fast pointer that moves two steps at a time.
Update your discrete logarithm parameters.
Solve a linear congruence upon collision.
Upon collision, one gets a congruence in the form:
am1 Bn1 ≡ am2 Bn2 mod (p)
Since a is a generator, the exponents must satisfy the linear congruence:
m1 - m2 ≡ x(n2 - n1) mod (p-1)
Solving this gives the value of x in ax ≡ B mod (p)
2.1.1. Coding Integer Pollard Rho
This section implements example 2 from (Pollard, 1978) where the goal is to find 2x ≡ 87833 mod 99989. The answer is 107. Here’s the link to the gist.
//Full guide: https://leetarxiv.substack.com/p/coding-guide-monte-carlo-methods
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>
#include <gmp.h>
//clear && gcc PollardRho_IntegerDLP.c -lm -lgmp -o m.o && ./m.o
void PollardRho_IntegerUpdate(mpz_t setElement, mpz_t exponentAnimal0, mpz_t exponentAnimal1, mpz_t desiredGenerator, mpz_t desiredResult, mpz_t primeNumberMinOne, mpz_t primeNumber, mpz_t temporary)
{
//Three update rules as defined in (Pollard, 1978)
int modulo = 3;
int moduloResult = mpz_mod_ui(temporary, setElement, modulo);
if(moduloResult == 2)
{
//Multiply the set element by desiredResult mod primeNumber
mpz_mul(setElement, setElement, desiredResult);
mpz_mod(setElement, setElement, primeNumber);
//Update exponentAnimal1 by 1 mod primeNumberMinOne
mpz_add_ui(exponentAnimal1, exponentAnimal1, 1);
mpz_mod(exponentAnimal1, exponentAnimal1, primeNumberMinOne);
}
else if(moduloResult == 1)
{
//Multiply the set element by desiredGenerator mod primeNumber
mpz_mul(setElement, setElement, desiredGenerator);
mpz_mod(setElement, setElement, primeNumber);
//Update exponentAnimal0 by 1 mod primeNumberMinOne
mpz_add_ui(exponentAnimal0, exponentAnimal0, 1);
mpz_mod(exponentAnimal0, exponentAnimal0, primeNumberMinOne);
}
else
{
//Square the set element and mod primeNumber
mpz_mul(setElement, setElement, setElement);
mpz_mod(setElement, setElement, primeNumber);
//Double both result and generator mod primeNumberMinOne
mpz_mul_ui(exponentAnimal0, exponentAnimal0, 2);
mpz_mul_ui(exponentAnimal1, exponentAnimal1, 2);
mpz_mod(exponentAnimal0, exponentAnimal0, primeNumberMinOne);
mpz_mod(exponentAnimal1, exponentAnimal1, primeNumberMinOne);
}
}
bool PollardRho_IntegerDLP()
{
bool result = false;
mpz_t primeNumber, primeNumberMinOne, desiredGenerator, desiredResult;
mpz_t exponentTortoise0, exponentTortoise, exponentTortoise1,setElementTortoise;
mpz_t exponentHare0, exponentHare, exponentHare1, setElementHare,temporary;
mpz_inits(exponentTortoise0, exponentTortoise, exponentTortoise1,setElementTortoise, exponentHare0, exponentHare, exponentHare1,setElementHare,temporary,primeNumber, primeNumberMinOne, desiredGenerator, desiredResult, NULL);
//Set prime number and primeNumber Minus One and desires
mpz_set_ui(primeNumber, 1019);
mpz_sub_ui(primeNumberMinOne, primeNumber, 1);
mpz_set_ui(desiredGenerator, 2);
mpz_set_ui(desiredResult, 5);
//Set generator and desired result
//Initialize generators, exponents and results to 0
mpz_set_ui(exponentTortoise0, 0);mpz_set_ui(exponentHare0, 0);
mpz_set_ui(exponentTortoise, 0);mpz_set_ui(exponentHare, 0);
mpz_set_ui(exponentTortoise1, 0);mpz_set_ui(exponentHare1, 0);
//Initialize our current set element to 1
mpz_set_ui(setElementTortoise, 1);
mpz_set_ui(setElementHare, 1);
for(int i = 0; i < 100; i++)
{
//Update tortoise once
PollardRho_IntegerUpdate(setElementTortoise, exponentTortoise0, exponentTortoise1, desiredGenerator, desiredResult, primeNumberMinOne, primeNumber, temporary);
//Update hare twice
PollardRho_IntegerUpdate(setElementHare, exponentHare0, exponentHare1, desiredGenerator, desiredResult, primeNumberMinOne, primeNumber, temporary);
PollardRho_IntegerUpdate(setElementHare, exponentHare0, exponentHare1, desiredGenerator, desiredResult, primeNumberMinOne, primeNumber, temporary);
//Print the current table
gmp_printf("%d:\nTortoise: (%Zd^%3Zd * %Zd^%3Zd = %5Zd)\n", i,desiredGenerator, exponentTortoise0, desiredResult, exponentTortoise1, setElementTortoise);
gmp_printf("Hare : (%Zd^%3Zd * %Zd^%3Zd = %5Zd)\n", desiredGenerator, exponentHare0, desiredResult, exponentHare1, setElementHare);
if(mpz_cmp(setElementTortoise, setElementHare) == 0)
{
printf("Found Collision\n");
break;
}
}
mpz_clears(exponentTortoise0, exponentTortoise, exponentTortoise1,setElementTortoise, exponentHare0, exponentHare, exponentHare1,setElementHare,temporary,primeNumber, primeNumberMinOne, desiredGenerator, desiredResult, NULL);
return result;
}
int main()
{
PollardRho_IntegerDLP();
return 0;
}
2.1.2. Coding Elliptic Curve Pollard Rho
This section illustrates Pollard’s Rho algorithm on elliptic curve discrete logarithm problems as described in (Menezes, Hankerson & Vanstone, 2004)7 on the curve:
We follow algorithm 4.3:

2.1.1.2. Important implementation gotchas:
Scalar multiplication is performed modulo prime number.
Partition function updates is performed modulo point order.
The discrete log denominator is d2-d1 while the numerator is c1-c2.
First, we implement a quick scalar multiplication library for our curve:
bool AddCurvePoints(EllipticCurvePoint R, EllipticCurvePoint P, EllipticCurvePoint Q, mpz_t aCurveParameter, mpz_t primeNumber)
{
//Case 0: Handle Points at infinity
if(P->infinity != 0){CopyPoint(Q,R);return true;}
if(Q->infinity != 0){CopyPoint(P,R);return true;}
//Case 1: Handle P->x == Q->x
if(mpz_cmp(P->x, Q->x) == 0)
{
//Case 1.1: X values similar but Y differ or 0
if(mpz_cmp(P->y, Q->y) != 0 || mpz_cmp_ui(P->y, 0) == 0){R->infinity = 1;return true;}
//Case 1.2: Point Doubling
mpz_t s, num, den, denominatorInverse, tmp;
mpz_inits(s, num, den, denominatorInverse, tmp, NULL);
//num = x^2
mpz_mul(num, P->x, P->x);
//num = 3x^2
mpz_mul_ui(num, num, 3);
//num = 3x^2 + a
mpz_add(num, num, aCurveParameter);
//den = 2y
mpz_mul_ui(den, P->y, 2);
//Find denominator inverse
if(!mpz_invert(denominatorInverse, den, primeNumber)){R->infinity = 1;mpz_clears(s, num, den, denominatorInverse, tmp, NULL);return true;}
//s = (3x^2)/(2y)
mpz_mul(s, num, denominatorInverse);
mpz_mod(s, s, primeNumber);
//x3 = s^2 - 2x
mpz_mul(tmp, s, s);
mpz_sub(tmp, tmp, P->x);
mpz_sub(tmp, tmp, Q->x);
mpz_mod(R->x, tmp, primeNumber);
//y3 = s*(x - x3) - y
mpz_sub(tmp, P->x, R->x);
mpz_mul(tmp, s, tmp);
mpz_sub(tmp, tmp, P->y);
mpz_mod(R->y, tmp, primeNumber);
R->infinity = 0;
mpz_clears(s, num, den, denominatorInverse, tmp, NULL);
return true;
}
//Case 2: Handle P != Q (Point Addition)
else
{
mpz_t s, num, den, denominatorInverse, tmp;
mpz_inits(s, num, den, denominatorInverse, tmp, NULL);
//num = y2 - y1
mpz_sub(num, Q->y, P->y);
//den = x2 - x1
mpz_sub(den, Q->x, P->x);
//Find inverse of denominator mod p
if(!mpz_invert(denominatorInverse, den, primeNumber)){R->infinity = 1;mpz_clears(s, num, den, denominatorInverse, tmp, NULL);return true;}
//s = (y2 - y1)/(x2 - x1) mod p
mpz_mul(s, num, denominatorInverse);mpz_mod(s, s, primeNumber);
//x3 = s^2 - x1 - x2 mod p
mpz_mul(tmp, s, s);mpz_sub(tmp, tmp, P->x);mpz_sub(tmp, tmp, Q->x);mpz_mod(R->x, tmp, primeNumber);
//y3 = s*(x1 - x3) - y1
mpz_sub(tmp, P->x, R->x);mpz_mul(tmp, s, tmp);mpz_sub(tmp, tmp, P->y);mpz_mod(R->y, tmp, primeNumber);
R->infinity = 0;
//Free memory
mpz_clears(s, num, den, denominatorInverse, tmp, NULL);
return true;
}
}
void CurveScalarMultiplication(EllipticCurvePoint resultant, EllipticCurvePoint generator, mpz_t privateKey, mpz_t aCurveParameter, mpz_t primeNumber)
{
//Create pointAtInfinity, temp0
EllipticCurvePoint pointAtInfinity = CreatePoint();
pointAtInfinity->infinity = 1;
EllipticCurvePoint temp0 = CreatePoint();
//Find no. of bits in private key
size_t binaryLength = mpz_sizeinbase(privateKey, 2);
//loop from MSB to LSB
for(ssize_t i = binaryLength - 1; i >= 0; --i)
{
//temp0 = 2*pointAtInfinity
AddCurvePoints(temp0, pointAtInfinity, pointAtInfinity, aCurveParameter, primeNumber);
CopyPoint(temp0, pointAtInfinity);
//Test the current bit's parity
if(mpz_tstbit(privateKey, i) != 0)
{
//temp0 = pointAtInfinity + generator
AddCurvePoints(temp0, pointAtInfinity, generator, aCurveParameter, primeNumber);
CopyPoint(temp0, pointAtInfinity);
}
}
//Save pointAtInfinity to the result variable
CopyPoint(pointAtInfinity, resultant);
//Free memory
DestroyPoint(pointAtInfinity);
DestroyPoint(temp0);
}
Next, we initialize a point P to (5,116) and another point Q to (155,166) and set curve parameters:
void TestPollardRho()
{
mpz_t discreteLog,primeNumber,a,b;
mpz_inits(discreteLog,primeNumber,a,b, NULL);
gmp_randstate_t state;
gmp_randinit_default(state);
gmp_randseed_ui(state, 2345);
EllipticCurvePoint temporaryPoint0 = CreatePoint();
EllipticCurvePoint temporaryPoint1 = CreatePoint();
EllipticCurvePoint P = CreatePoint();
EllipticCurvePoint Q = CreatePoint();
//Set curve parameters
mpz_set_ui(a, 1);
mpz_set_ui(b, 44);
//Set P to (5,116)
mpz_set_ui(P->x, 5);
mpz_set_ui(P->y, 116);
//Set Q to (5,116)
mpz_set_ui(Q->x, 155);
mpz_set_ui(Q->y, 166);
//Set primenumber to 229
mpz_set_ui(primeNumber, 229);
int partitionCount = 4;
PartitionFunction *partitions = malloc(partitionCount * sizeof(PartitionFunction));
for(int i = 0; i < partitionCount; i++)
{
partitions[i] = CreatePartitionFunction();
InitializePartitionFunction(state, partitions[i], P, Q, temporaryPoint0,temporaryPoint1,a, primeNumber);
PrintPartitionFunction(partitions[i]);
}
DestroyPoint(P);DestroyPoint(Q);DestroyPoint(temporaryPoint0);DestroyPoint(temporaryPoint1);
mpz_clears(discreteLog,primeNumber,a,b, NULL);gmp_randclear(state);
for(int i = 0; i < partitionCount; i++){DestroyPartitionFunction(partitions[i]);}free(partitions);
}
We proceed to establish a partition function where H(x,y) = (x mod 4) + 1. That is, for each x-coordinate, we modulo by 4.
Now, we find a, b, R triples for each H section:
void InitializePartitionFunction(gmp_randstate_t state, PartitionFunction partitionFunction, EllipticCurvePoint P, EllipticCurvePoint Q, EllipticCurvePoint temporaryPoint0,EllipticCurvePoint temporaryPoint1, mpz_t aCurveParameter, mpz_t primeNumber)
{
//Select random a, b in [0, n-1]
mpz_urandomm(partitionFunction->a, state, primeNumber);
mpz_urandomm(partitionFunction->b, state, primeNumber);
//Compute R = a*P + b*Q
CurveScalarMultiplication(temporaryPoint0, P, partitionFunction->a, aCurveParameter,primeNumber);
CopyPoint(temporaryPoint0,partitionFunction->R);
CurveScalarMultiplication(temporaryPoint0, Q, partitionFunction->b, aCurveParameter,primeNumber);
AddCurvePoints(temporaryPoint1, partitionFunction->R, temporaryPoint0, aCurveParameter,primeNumber);
CopyPoint(temporaryPoint1,partitionFunction->R);
}
The entire file is available at this GitHub Gist.
We observe a collision after 137 updates:
According to the algorithm, 215 P + 16 Q = 87 P + 234 Q.
Our discrete logarithm is l = (215-87) * (234-16)-1 mod 239 = 176.
2.2 Pollard’s Improvement on Shanks Algorithm
Pollard makes a sidenote on improving the running time of his Rho algorithm. According to Pollard, if one can factorize (p-1) then one can run the Rho algorithm on the factors of (p-1) then later combine the results.
2.3 Pollard’s Kangaroo Method
Pollard’s Kangaroo algorithm, also called Pollard’s Lambda (Pollard, 2000)8 method is useful when the discrete logarithm is known to lie within a certain interval.
Pollard’s Kangaroo is particularly useful when we know where to search, like in the bitcoin puzzles.
One must note however, Pollard’s Kangaroo may fail sometimes.
Pollard writes this (paraphrased) analogy:
Imagine you’re hunting wild kangaroos in a forest you know rather well. Imagine you have a tame kangaroo side kick. Let your tame kangaroo jump around while you follow and dig holes along its path. If you do this enough times, one of the wild kangaroos will eventually fall inside one of your dug holes.
2.3.1 Coding Pollard’s Kangaroo
This section leaves to the reader example 4 from (Pollard, 1978) where the goal is to find 8x ≡ 428 mod 99989 when we know x lies within the range -5000 and 0. We know the value of x is -4051.
Pollard gives a detailed example for integer logarithms. We shall focus on the elliptic curve version.
2.3.1.1 Pollard’s Kangaroo on Elliptic Curves
We implement Pollard’s Kangaroo as described in (Gill, 2019)9 in Python.
import hashlib
import random
import time
class EllipticCurve:
def __init__(self, a, b, p):
self.a = a
self.b = b
self.p = p
def is_on_curve(self, point):
if point is None:
return True
x, y = point
return (y * y) % self.p == (x * x * x + self.a * x + self.b) % self.p
def add(self, point1, point2):
if point1 is None:
return point2
if point2 is None:
return point1
x1, y1 = point1
x2, y2 = point2
if x1 == x2 and y1 != y2:
return None
if x1 == x2:
m = (3 * x1 * x1 + self.a) * pow(2 * y1, self.p - 2, self.p) % self.p
else:
m = (y2 - y1) * pow(x2 - x1, self.p - 2, self.p) % self.p
x3 = (m * m - x1 - x2) % self.p
y3 = (m * (x1 - x3) - y1) % self.p
return (x3, y3)
def multiply(self, point, scalar):
result = None
current = point
while scalar:
if scalar & 1:
result = self.add(result, current)
current = self.add(current, current)
scalar //= 2
return result
def hash_point(point, p):
"""Hash function for determining jumps"""
x, y = point
hash_input = f"{x},{y}".encode()
return int(hashlib.sha256(hash_input).hexdigest(), 16) % 8 + 1 # Smaller jumps for smaller range
def pollard_kangaroo(curve, G, Q, a, b, max_iterations=1000):
"""
Pollard's Kangaroo algorithm to solve k*G = Q where k in [a, b]
"""
# Number of jumps (should be around sqrt(b-a))
N = int((b - a) ** 0.5) + 1
print(f"Using {N} tame kangaroos")
# Tame kangaroo (starts at known position)
x_t = 0 # Distance traveled by tame kangaroo
T = curve.multiply(G, b) # Tame kangaroo starts at b*G
# Wild kangaroo (starts at unknown position)
x_w = 0 # Distance traveled by wild kangaroo
W = Q # Wild kangaroo starts at Q = k*G
# Store positions of tame kangaroos
tame_positions = {}
# Let tame kangaroo make N jumps
for i in range(N):
jump_size = hash_point(T, curve.p)
x_t += jump_size
T = curve.add(T, curve.multiply(G, jump_size))
# Store position with distance
tame_positions[T] = b + x_t
print(f"Tame kangaroo finished after {N} jumps")
print(f"Tame kangaroo final position: {T}")
# Let wild kangaroo jump until it meets a tame kangaroo
for i in range(max_iterations):
if i % 10 == 0 and i > 0:
print(f"Wild kangaroo iteration {i}, current position: {W}")
jump_size = hash_point(W, curve.p)
x_w += jump_size
W = curve.add(W, curve.multiply(G, jump_size))
# Check if wild kangaroo found a tame kangaroo's position
if W in tame_positions:
tame_distance = tame_positions[W]
wild_distance = x_w
k = tame_distance - wild_distance
# Verify the solution
if curve.multiply(G, k) == Q:
print(f"Found private key k = {k} after {i} wild kangaroo jumps")
return k
print("Failed to find private key within maximum iterations")
return None
# Define the elliptic curve: y^2 = x^3 + 7 mod 239
curve = EllipticCurve(a=0, b=7, p=239)
# Generator point G (choose a point on the curve)
G = None
for x in range(1, 239):
y_sq = (x**3 + 7) % 239
for y in range(239):
if (y * y) % 239 == y_sq:
G = (x, y)
break
if G:
break
print(f"Generator point G = {G}")
# Verify G is on the curve
assert curve.is_on_curve(G), "Generator point is not on the curve"
# Create a test private key in the range [0, 64]
private_key = random.randint(0, 64)
print(f"Test private key: {private_key}")
# Calculate Q = k*G
Q = curve.multiply(G, private_key)
print(f"Public key Q = {Q}")
# Run Pollard's Kangaroo algorithm
start_time = time.time()
found_key = pollard_kangaroo(curve, G, Q, 0, 64)
end_time = time.time()
if found_key is not None:
print(f"Success! Found private key: {found_key}")
print(f"Original private key: {private_key}")
print(f"Keys match: {found_key == private_key}")
print(f"Time taken: {end_time - start_time:.4f} seconds")
# Verify the solution
test_Q = curve.multiply(G, found_key)
print(f"Verification: {found_key}*G = {test_Q}")
print(f"Matches Q: {test_Q == Q}")
else:
print("Failed to find the private key")
The code is available here.
In this example, we set the search range of out private key to the integer 64. (The entire search space is modulo 239).
As expected, the Kangaroo algorithm solved the DLP in 9 steps ~ the square root of 64.
The generator point we found here has low order, so 103 == 23 in this example.
Now, go mine Satoshi’s billion dollar wallet lol
References
Pollard, J. M. (1978). Monte Carlo methods for index computation (mod p). Mathematics of Computation, 32(143), 918–924. PDF.
Howell, S,. J,. (1998). The Index Calculus Algorithm for Discrete Logarithms. Clemson University Masters Thesis.
Silverman, J,. H,. (2007). The Four Faces of Lifting for the Elliptic Curve Discrete Logarithm Problem. 11th Workshop on Elliptic Curve Cryptography. Link.
Knuth, D. E. (1969). The Art of Computer Programming, vol. II: Seminumerical Algorithms, Addison-Wesley, p. 7, exercises 6 and 7
Menezes, A., Hankerson, D., and Vanstone, S. (2004). Guide to Elliptic Curve Cryptography. Springer.
Pollard, J. M. (2000). Kangaroos, monopoly and discrete logarithms. Journal of Cryptology, 13(4), 437–447. https://doi.org/10.1007/s001450010010