from collections import Counter, defaultdict
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
Gx = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798
Gy = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
def mod_inverse(a: int, m: int) -> int:
if a < 0:
a = a % m
g, x, _ = extended_gcd(a, m)
if g != 1:
raise ValueError("No inverse")
return x % m
def extended_gcd(a: int, b: int):
if a == 0:
return b, 0, 1
gcd, x1, y1 = extended_gcd(b % a, a)
return gcd, y1 - (b // a) * x1, x1
def point_add(p1, p2):
if p1 is None:
return p2
if p2 is None:
return p1
x1, y1 = p1
x2, y2 = p2
if x1 == x2:
if y1 != y2:
return None
s = (3 * x1 * x1 * mod_inverse(2 * y1, P)) % P
else:
s = ((y2 - y1) * mod_inverse(x2 - x1, P)) % P
x3 = (s * s - x1 - x2) % P
y3 = (s * (x1 - x3) - y1) % P
return (x3, y3)
def scalar_mult(k: int, point):
if k == 0:
return None
result = None
addend = point
while k:
if k & 1:
result = point_add(result, addend)
addend = point_add(addend, addend)
k >>= 1
return result
G = (Gx, Gy)
def analyze_power_of_2_keys():
print("=" * 70)
print("POWERS OF 2 ANALYSIS")
print("=" * 70)
print("\nPublic keys for 2^n:")
print(f"{'n':>3} | {'X (first 16 hex)':^20} | {'Y parity':^8} | X mod 256")
print("-" * 70)
for n in range(0, 40):
k = 1 << n
point = scalar_mult(k, G)
x, y = point
x_hex = format(x, "064x")[:16]
parity = "even" if y % 2 == 0 else "odd"
x_mod = x % 256
print(f"{n:3} | {x_hex:^20} | {parity:^8} | {x_mod:3}")
def analyze_sequential_structure():
print("\n" + "=" * 70)
print("SEQUENTIAL STRUCTURE ANALYSIS")
print("=" * 70)
points = []
current = G
points.append((1, current))
for k in range(2, 1001):
current = point_add(current, G)
points.append((k, current))
print("\nCorrelation: private key bits vs X coordinate bits")
print("(Looking for any predictable relationship)")
for bit in range(8):
matches = 0
for k, (x, y) in points:
k_bit = (k >> bit) & 1
x_bit = (x >> bit) & 1
if k_bit == x_bit:
matches += 1
ratio = matches / len(points)
deviation = abs(ratio - 0.5) * 100
marker = "***" if deviation > 10 else ""
print(f" Bit {bit}: {ratio:.3f} correlation {marker}")
print("\nX mod small numbers vs k mod same:")
for m in [3, 5, 7, 11, 13]:
correlation = defaultdict(lambda: defaultdict(int))
for k, (x, y) in points:
correlation[k % m][x % m] += 1
predictable = True
for k_mod in range(m):
counts = correlation[k_mod]
if len(counts) > 1:
max_count = max(counts.values())
total = sum(counts.values())
if max_count / total < 0.9:
predictable = False
break
status = "PREDICTABLE!" if predictable else "random"
print(f" mod {m:2}: {status}")
def analyze_halving_relationship():
print("\n" + "=" * 70)
print("HALVING/DOUBLING RELATIONSHIP")
print("=" * 70)
print("\nP(2k) vs P(k) relationship:")
print("P(2k) = 2*P(k) (point doubling)")
print("\nChecking if X(2k) has predictable relationship to X(k):")
for k in [1, 2, 3, 5, 7, 11, 13, 17, 19, 100, 1000]:
pk = scalar_mult(k, G)
p2k = scalar_mult(2 * k, G)
p2k_doubled = point_add(pk, pk)
assert p2k == p2k_doubled, "Point doubling mismatch!"
xk = pk[0]
x2k = p2k[0]
ratio = x2k / xk if xk != 0 else 0
xor_high = (xk ^ x2k) >> 248
print(f" k={k:5}: X(k)/X(2k) high XOR = 0x{xor_high:02x}")
def analyze_additive_structure():
print("\n" + "=" * 70)
print("ADDITIVE STRUCTURE (the key property)")
print("=" * 70)
print("\nP(a+b) = P(a) + P(b) - this is how Kangaroo works!")
print("If we find structure here, we can optimize search.\n")
print("Testing: Can we predict bits of X(a+b) from X(a) and X(b)?")
results = []
for a in range(1, 101):
for b in range(1, 101):
if a == b:
continue
pa = scalar_mult(a, G)
pb = scalar_mult(b, G)
pab = scalar_mult(a + b, G)
xa, xb, xab = pa[0], pb[0], pab[0]
xor_ab = xa ^ xb
xor_result = xab ^ xor_ab
results.append((a, b, xa, xb, xab, xor_result))
high_bytes = [r[5] >> 248 for r in results]
byte_counts = Counter(high_bytes)
print("\nHigh byte of (X(a+b) XOR (X(a) XOR X(b))) distribution:")
for byte, count in byte_counts.most_common(10):
print(f" 0x{byte:02x}: {count} ({100 * count / len(results):.1f}%)")
print("\nChecking predictability of each bit of X(a+b):")
for bit in [0, 1, 2, 3, 4, 5, 6, 7, 255, 254, 253]:
correct = 0
for a, b, xa, xb, xab, _ in results:
predicted = ((xa >> bit) & 1) ^ ((xb >> bit) & 1)
actual = (xab >> bit) & 1
if predicted == actual:
correct += 1
accuracy = correct / len(results)
marker = "***" if abs(accuracy - 0.5) > 0.1 else ""
print(f" Bit {bit:3}: XOR prediction accuracy = {accuracy:.3f} {marker}")
def analyze_distance_from_known():
print("\n" + "=" * 70)
print("DISTANCE-BASED INFERENCE")
print("=" * 70)
print("\nGiven P(k), can we narrow down candidates for P(k+d)?")
print("This is what would reduce Kangaroo complexity.\n")
d_points = {}
for d in range(1, 257):
d_points[d] = scalar_mult(d, G)
print("Checking if X(k+d) - X(k) has structure based on d:")
for d in [1, 2, 4, 8, 16, 32, 64, 128, 256]:
diffs = []
for k in range(1, 501):
pk = scalar_mult(k, G)
pkd = scalar_mult(k + d, G)
diff = (pkd[0] - pk[0]) % P
diffs.append(diff)
unique_diffs = len(set(diffs))
print(f" d={d:3}: {unique_diffs} unique X differences out of 500")
def analyze_negation_symmetry():
print("\n" + "=" * 70)
print("NEGATION SYMMETRY")
print("=" * 70)
print("\nFor P(k), we have P(N-k) = -P(k) = (X(k), -Y(k))")
print("This means X(k) = X(N-k) - same X coordinate!\n")
print("This is already exploited: search [0, N/2] covers all X values")
print("\nFor puzzle with range [start, start+2^r]:")
print("If range includes both k and N-k, we get 'free' collision")
for bits in [20, 30, 40, 50]:
range_size = 1 << bits
print(
f" {bits}-bit range: N-k distance = ~{(N - range_size) // (1 << bits)} range sizes away"
)
def analyze_multipoint_relationships():
print("\n" + "=" * 70)
print("MULTI-POINT RELATIONSHIPS")
print("=" * 70)
print("\nChecking if knowing P(a), P(b), P(c) helps predict P(a+b-c):")
samples = []
for _ in range(1000):
import random
a = random.randint(1, 10000)
b = random.randint(1, 10000)
c = random.randint(1, min(a + b - 1, 10000))
pa = scalar_mult(a, G)
pb = scalar_mult(b, G)
pc = scalar_mult(c, G)
pabc = scalar_mult(a + b - c, G)
samples.append((pa[0], pb[0], pc[0], pabc[0]))
print("\nTrying prediction formulas for X(a+b-c):")
formulas = [
("XOR all", lambda xa, xb, xc: xa ^ xb ^ xc),
("(xa + xb - xc) mod P", lambda xa, xb, xc: (xa + xb - xc) % P),
(
"(xa * xb / xc) mod P",
lambda xa, xb, xc: (xa * xb * mod_inverse(xc, P)) % P if xc != 0 else 0,
),
]
for name, formula in formulas:
correct_bits = [0] * 8
for xa, xb, xc, xabc in samples:
try:
predicted = formula(xa, xb, xc)
for bit in range(8):
if ((predicted >> bit) & 1) == ((xabc >> bit) & 1):
correct_bits[bit] += 1
except:
pass
accuracies = [c / len(samples) for c in correct_bits]
max_acc = max(accuracies)
print(f" {name}: max bit accuracy = {max_acc:.3f}")
def main():
print("=" * 70)
print("DEEP PATTERN ANALYSIS FOR KANGAROO OPTIMIZATION")
print("Looking for exploitable structure in secp256k1")
print("=" * 70)
analyze_power_of_2_keys()
analyze_sequential_structure()
analyze_halving_relationship()
analyze_additive_structure()
analyze_distance_from_known()
analyze_negation_symmetry()
analyze_multipoint_relationships()
print("\n" + "=" * 70)
print("CONCLUSIONS")
print("=" * 70)
print("""
Key findings for Kangaroo optimization:
1. NEGATION SYMMETRY: X(k) = X(N-k) - already known, halves search space
2. ADDITIVE PROPERTY: P(a+b) = P(a) + P(b) - this is what Kangaroo exploits
3. NO PREDICTABLE BITS: Cannot predict X(a+b) bits from X(a), X(b) bits
4. POTENTIAL OPTIMIZATIONS:
- Better jump table design (current: powers of 2)
- Batch inversion for faster point operations
- Memory-time tradeoff (store more distinguished points)
- GPU parallelization (already implemented)
5. THEORETICAL LIMITS:
- O(√n) is believed optimal for generic group algorithms
- Improvement requires exploiting non-generic structure
- secp256k1 has no known weak structure
""")
if __name__ == "__main__":
main()