Pages:
Author

Topic: Bitcoin puzzle transaction ~32 BTC prize to who solves it - page 36. (Read 245568 times)

hero member
Activity: 862
Merit: 662
hello! I'm sorry, it seemed to me or you explained everything somehow difficult. to solve the problem.

Difficult?

It clear like a water!!

can I write to you in private messages?

TBH i don't have enough time for this, so the answer is no.
newbie
Activity: 23
Merit: 0
Together with the target address I will also provide the range of the search interval.

This will be in the form (just an example):

Code:
minKey = 0xf2e542b46066c4e6f91abc80000000000000000000185e689447431d74c5b133
maxKey = 0xf2e542b46066c4e6f91abcbfffffffffffffffffffd85e689447431d74c5b133

The Hamming length of the range will therefore be 80 contiguous bits, but they may start anywhere.


Nice Challenge

Well If you provide the EXACT range it can be solve subtracting the bits that remain same in both start an end range example for some key as your example:

Code:
StarRange: f2e542b46066c4e6f91abc80000000000000000000185e689447431d74c5b133
ENDRange:  f2e542b46066c4e6f91abcbfffffffffffffffffff185e689447431d74c5b133

Key:       f2e542b46066c4e6f91abcc206b831c41b15d6bf49185e689447431d74c5b133
Publickey: 0259f57acc739162a68785c08baf201da1b2797d90ded6cd5dde5c3f26b6df24ae
MSB      : f2e542b46066c4e6f91abc000000000000000000000000000000000000000000


Subtracting Most significan bits:

Code:
./keymath 0259f57acc739162a68785c08baf201da1b2797d90ded6cd5dde5c3f26b6df24ae - 0xf2e542b46066c4e6f91abc000000000000000000000000000000000000000000
Result: 03dc89a446662604456da1f1fdfba0e578fb69e9750751081b3e070cb747280bfc

With this step the original  key may look like: 0xc206b831c41b15d6bf49185e689447431d74c5b133

Subtracting Less significan bits:

LSB:       000000000000000000000000000000000000000000185e689447431d74c5b133

Code:
./keymath 03dc89a446662604456da1f1fdfba0e578fb69e9750751081b3e070cb747280bfc - 0x185e689447431d74c5b133
Result: 029659ffde564a27b7e6ea76936dcc6c33ef969d1a089a05214151ebc9ef4f9db7

With this step the original key may look like: 0xc206b831c41b15d6bf490000000000000000000000

Now we need to remove zeros on the right:

0xc206b831c41b15d6bf490000000000000000000000
                   0x10000000000000000000000

Code:
./keymath 029659ffde564a27b7e6ea76936dcc6c33ef969d1a089a05214151ebc9ef4f9db7 / 0x10000000000000000000000
Result: 027fb98c7edb7aef934a25eaf02384dbb42a9b8a32a85b451a5e13e5ebdd613f85

With this step the original key may look like: 0xc206b831c41b15d6bf49
So once that you solve the public key: 027fb98c7edb7aef934a25eaf02384dbb42a9b8a32a85b451a5e13e5ebdd613f85

You need to do do the same steps backwards to get the original key.

So this is just a simple 80 bit challenge that can be solve with kangaroo:

Now

Currently I have a 80-bits solver DP database that can solve any key in under 6 seconds on a GPU, and under a minute if using the CPU.

If you can solve it under 6 seconds on GPU or under a minute un CPU then this is not any challenge if you already have the right tools and scripts to the operations on the public key.

Since time ago you have such DP dataset the I bet nobody is going to beat you

Well, that's it, we're screwed. My 3-kangaroo C program also runs on Raspberry Pi. I also have it an Android app with native JNI bindings to it. So every time I wake it up it runs a few batches to mine DPs. Next step is to implement kang on mobile GPUs using Vulkan.


hello! I'm sorry, it seemed to me or you explained everything somehow difficult. to solve the problem, you need to change the generation of kangaroos or bsgs. so that she leaves the beginning and the end and goes through the middle (the 80-bit segment selected by him in a 256-bit key). the public key, he will give. I'm sorry again...
and also, I have one question for you, can I write to you in private messages?
hero member
Activity: 862
Merit: 662
Together with the target address I will also provide the range of the search interval.

This will be in the form (just an example):

Code:
minKey = 0xf2e542b46066c4e6f91abc80000000000000000000185e689447431d74c5b133
maxKey = 0xf2e542b46066c4e6f91abcbfffffffffffffffffffd85e689447431d74c5b133

The Hamming length of the range will therefore be 80 contiguous bits, but they may start anywhere.


Nice Challenge

Well If you provide the EXACT range it can be solve subtracting the bits that remain same in both start an end range example for some key as your example:

Code:
StarRange: f2e542b46066c4e6f91abc80000000000000000000185e689447431d74c5b133
ENDRange:  f2e542b46066c4e6f91abcbfffffffffffffffffff185e689447431d74c5b133

Key:       f2e542b46066c4e6f91abcc206b831c41b15d6bf49185e689447431d74c5b133
Publickey: 0259f57acc739162a68785c08baf201da1b2797d90ded6cd5dde5c3f26b6df24ae
MSB      : f2e542b46066c4e6f91abc000000000000000000000000000000000000000000


Subtracting Most significan bits:

Code:
./keymath 0259f57acc739162a68785c08baf201da1b2797d90ded6cd5dde5c3f26b6df24ae - 0xf2e542b46066c4e6f91abc000000000000000000000000000000000000000000
Result: 03dc89a446662604456da1f1fdfba0e578fb69e9750751081b3e070cb747280bfc

With this step the original  key may look like: 0xc206b831c41b15d6bf49185e689447431d74c5b133

Subtracting Less significan bits:

LSB:       000000000000000000000000000000000000000000185e689447431d74c5b133

Code:
./keymath 03dc89a446662604456da1f1fdfba0e578fb69e9750751081b3e070cb747280bfc - 0x185e689447431d74c5b133
Result: 029659ffde564a27b7e6ea76936dcc6c33ef969d1a089a05214151ebc9ef4f9db7

With this step the original key may look like: 0xc206b831c41b15d6bf490000000000000000000000

Now we need to remove zeros on the right:

0xc206b831c41b15d6bf490000000000000000000000
                   0x10000000000000000000000

Code:
./keymath 029659ffde564a27b7e6ea76936dcc6c33ef969d1a089a05214151ebc9ef4f9db7 / 0x10000000000000000000000
Result: 027fb98c7edb7aef934a25eaf02384dbb42a9b8a32a85b451a5e13e5ebdd613f85

With this step the original key may look like: 0xc206b831c41b15d6bf49
So once that you solve the public key: 027fb98c7edb7aef934a25eaf02384dbb42a9b8a32a85b451a5e13e5ebdd613f85

You need to do do the same steps backwards to get the original key.

So this is just a simple 80 bit challenge that can be solve with kangaroo:

Now

Currently I have a 80-bits solver DP database that can solve any key in under 6 seconds on a GPU, and under a minute if using the CPU.

If you can solve it under 6 seconds on GPU or under a minute un CPU then this is not any challenge if you already have the right tools and scripts to the operations on the public key.

Since time ago you have such DP dataset the I bet nobody is going to beat you

Well, that's it, we're screwed. My 3-kangaroo C program also runs on Raspberry Pi. I also have it an Android app with native JNI bindings to it. So every time I wake it up it runs a few batches to mine DPs. Next step is to implement kang on mobile GPUs using Vulkan.

member
Activity: 873
Merit: 22
$$P2P BTC BRUTE.JOIN NOW ! https://uclck.me/SQPJk
Digaran, Cobras, etc. go to bed please

)))

use DD = (P - D) %N as a P many times, result  from 2^120 to
print( 0x29d7a836ea187f2e1fc2 -2**77) 78 bit.This is by hands calculation, without for/wile - not many pubkeys.
hero member
Activity: 630
Merit: 731
Bitcoin g33k
Digaran, Cobras, etc. go to bed please
member
Activity: 873
Merit: 22
$$P2P BTC BRUTE.JOIN NOW ! https://uclck.me/SQPJk
How to get 2^60 publick or privkey from 120bit privkey. This is not ready to use, but, maybe someone will be interested:



Code:

N = 115792089237316195423570985008687907852837564279074904382605163141518161494337

# Фyнкция для нaxoждeния oбpaтнoгo элeмeнтa
def inv(v):
    return pow(v, N - 2, N)
def divnum(a, b):
    return (a * inv(b)) % N



target = 2**40 #this is what thant in result

divider = 2**40

P= 0x0000000000000000000000000000000000b10f22572c497a836ea187f2e1fc23 #privkey of 2**120




D = (target * divider) %N

DD = (P - D) %N

print(hex(DD*1%N))

F = ((P -DD %N)*1%N)%N

print(hex(F))

result= divnum(F,divider)

print(hex(result))








output:

0xb10f22572b497a836ea187f2e1fc23
0x100000000000000000000
0x10000000000


for get 2**40  priv after dividing to 2**40,  from 2**120 priv need  substract  from 2**120 priv this number 0xb10f22572b497a836ea187f2e1fc23 and divide to 2**40

any ideas ?
member
Activity: 165
Merit: 26
Well, that's it, we're screwed. My 3-kangaroo C program also runs on Raspberry Pi. I also have it an Android app with native JNI bindings to it. So every time I wake it up it runs a few batches to mine DPs. Next step is to implement kang on mobile GPUs using Vulkan.

I'm now looking for a place to rent ASICs where I can run custom software. However, there are firmware restrictions. Most ASICs, such as those from Bitmain (Antminer) or MicroBT (Whatsminer), come with firmware that may limit the ability to install custom software.

So, I guess you're going after #67 / 68 / etc? Cheesy

At this point, we might as well send an inquiry directly to TSMC and ask them how much they'd ask for a custom 3nm secp256k1 SoC. I don't think it's an issue for them, surely NSA already bought a ton of those by now.
newbie
Activity: 18
Merit: 0
I'm now looking for a place to rent ASICs where I can run custom software. However, there are firmware restrictions. Most ASICs, such as those from Bitmain (Antminer) or MicroBT (Whatsminer), come with firmware that may limit the ability to install custom software.
Did you check bitaxe family ?
member
Activity: 503
Merit: 38
Well, that's it, we're screwed. My 3-kangaroo C program also runs on Raspberry Pi. I also have it an Android app with native JNI bindings to it. So every time I wake it up it runs a few batches to mine DPs. Next step is to implement kang on mobile GPUs using Vulkan.


I'm now looking for a place to rent ASICs where I can run custom software. However, there are firmware restrictions. Most ASICs, such as those from Bitmain (Antminer) or MicroBT (Whatsminer), come with firmware that may limit the ability to install custom software.
member
Activity: 165
Merit: 26
Puzzle #60 with a MacBook M1 Max
4.01e+03 seconds is 4010 seconds. So 1 hour and 11 minutes
I'm curious how much would it be with multithreading.

Same speed. Python uses GIL by default, so all threads are actually sequential (interleaved) at the interpreter level, unless you mix in some native libraries and disable GIL while they run.

You'd need to run multiple processes instead (multiprocessing module) but at that point you'll need some way to have a shared concurrency-safe hashmap.

Moral? it's just a high-level kang, not a high-performance kang.

Btw...These days, I'm implementing Kangaroo in VHDL.  Grin

Well, that's it, we're screwed. My 3-kangaroo C program also runs on Raspberry Pi. I also have it an Android app with native JNI bindings to it. So every time I wake it up it runs a few batches to mine DPs. Next step is to implement kang on mobile GPUs using Vulkan.
member
Activity: 503
Merit: 38
LE: oh ok, you said single core, but still... should be 10x - 15x faster than 600k/s.

Rust is 15-20 times faster, but it depends on the architecture.

Btw...These days, I'm implementing Kangaroo in VHDL.  Grin

Code:
architecture fsm of scalar_mult is
  type state_type is (INIT, DOUBLE_ADD, DONE);
  signal state : state_type := INIT;
  signal k : unsigned(255 downto 0); -- Scalar
  signal P, Q : point_type; -- Points on the elliptic curve

begin
  process(clk, reset)
  begin
    if reset = '1' then
      state <= INIT;
    elsif rising_edge(clk) then
      case state is
        when INIT =>
          -- Set up initial values
          Q <= identity_point; -- Q = O
          P <= input_point;    -- P = input point
          state <= DOUBLE_ADD;

        when DOUBLE_ADD =>
          if k(255) = '1' then
            Q <= point_add(Q, P); -- Q = Q + P
          end if;
          P <= point_double(P);   -- P = 2P
          k <= k(254 downto 0) & '0'; -- Shift scalar
          if k = 0 then
            state <= DONE;
          end if;

        when DONE =>
          result <= Q; -- Output the result
      end case;
    end if;
  end process;
end architecture;

and so on.......
member
Activity: 122
Merit: 11
Puzzle #60 with a MacBook M1 Max
4.01e+03 seconds is 4010 seconds. So 1 hour and 11 minutes
I'm curious how much would it be with multithreading.

Code:
Ops: 1640142848 Table size: 25626624 Speed: 408909 ops/s
Ops: 1640964096 Table size: 25639463 Speed: 408910 ops/s
Private Key: 0xfc07a1825367bbe
Ops: 1641142272 Stored: 25642283
Speed: 408885 ops/s
Finished in 4.01e+03 s


What's the point of all of this ?  You will never solve puzzle #135 with consumer class CPU.  Lower ranges can be solved with currently existing tools much faster  than python script...
newbie
Activity: 18
Merit: 0
Puzzle #60 with a MacBook M1 Max
4.01e+03 seconds is 4010 seconds. So 1 hour and 11 minutes
I'm curious how much would it be with multithreading.

Code:
Ops: 1640142848 Table size: 25626624 Speed: 408909 ops/s
Ops: 1640964096 Table size: 25639463 Speed: 408910 ops/s
Private Key: 0xfc07a1825367bbe
Ops: 1641142272 Stored: 25642283
Speed: 408885 ops/s
Finished in 4.01e+03 s
member
Activity: 165
Merit: 26

Code:
Private Key: 0xade6d7ce3b9b
Ops: 8690688 Stored: 135855
Speed: 396380 ops/s
Finished in 21.9 s


  • KANGAROO: Thu Oct 17 08:35:42 2024
  • [Puzzle]: 50
  • Average time to solve: 47 sec

It averages 625,037 h/s on a single core (AMD Ryzen 7 5700X). This is in full CPU mode. I achieve the best results on the Ampere® Altra® Q80-30 in multicore mode.

Isn't a multicore full CPU mode that achieves like 50% higher speed than a single-threaded Python script, like... very slow? You're still not doing this?

LE: oh ok, you said single core, but still... should be 10x - 15x faster than 600k/s.
member
Activity: 503
Merit: 38
I achieve the best results on the Ampere® Altra® Q80-30 in multicore mode.

Do you have hosting on Ampere with Hetzner as your hosting provider?  Wink

Sure, I also have a pet unicorn that helps me manage the cluster.   Grin
jr. member
Activity: 42
Merit: 0
I achieve the best results on the Ampere® Altra® Q80-30 in multicore mode.

Do you have hosting on Ampere with Hetzner as your hosting provider?  Wink
member
Activity: 503
Merit: 38

Code:
Private Key: 0xade6d7ce3b9b
Ops: 8690688 Stored: 135855
Speed: 396380 ops/s
Finished in 21.9 s


  • KANGAROO: Thu Oct 17 08:35:42 2024
  • [Puzzle]: 50
  • [Lower range limit]: 562949953421312
  • [Upper range limit]: 1125899906842623
  • [EC Point Coordinate X]: 110560903758971929709743161563183868968201998016819862389797221564458485814982
  • [EC Point Coordinate Y]: 106403041512432555215316396882584033752066537554388330180776939978150437217531
  • [Expected Hops: 2^25.50 (47453132)]
  • [Hops: 2^24.81 <-> 625037 h/s] [00:00:47]
  • PUZZLE SOLVED: Thu Oct 17 08:36:29 2024
  • Private key (dec): 611140496167764
  • Hops: 29560288
  • Average time to solve: 47 sec

It averages 625,037 h/s on a single core (AMD Ryzen 7 5700X). This is in full CPU mode. I achieve the best results on the Ampere® Altra® Q80-30 in multicore mode.
newbie
Activity: 23
Merit: 0
On Nov. 1st 2024 at 0:00 AM UTC I will post here a BTC P2PKH address (compressed) associated with an 80-bits secure private key.

The address owns 0.005 BTC (~340 $)

Starting from that time, at some point during the next 24 hours an outgoing transaction, containing unspent outputs from that address, will be broadcasted on the BTC network (public mempool, not Mara).

Let's see who breaks it. I only ask one thing if that happens: what method was used to break the key.

Together with the target address I will also provide the range of the search interval.

This will be in the form (just an example):

Code:
minKey = 0xf2e542b46066c4e6f91abc80000000000000000000185e689447431d74c5b133
maxKey = 0xf2e542b46066c4e6f91abcbfffffffffffffffffffd85e689447431d74c5b133

The Hamming length of the range will therefore be 80 contiguous bits, but they may start anywhere.

If your first instinct is to yell "this is not a 80-bits secure private key", please note this competition is NOT for you, do not expect a response from me.

Get your tools ready boys.
let someone be lucky) if a transaction takes place from that address and the public key is known...The case is 5 minutes. Even with my capabilities, it will be very fast to find him. I won't participate, just watch.(OK, not 5 minutes, but 10, sorry...)))


Total range will  be 256 - 80. Too big, you not think so ?
if an 80-bit interval is specified in the 256-bit range, this will not increase or complicate the search in any way.
?
Activity: -
Merit: -
member
Activity: 165
Merit: 26
Please correct me if I am wrong kTimesG

If I'd be any of you, I'd let anyone that thinks that an "contiguous 80-bits missing and 256 - 80 bits known" problem is a 256-bits problem, to continue in believing so... rather than getting annoyed trying to explain some obvious things, maybe speed up your Python.

Code:
import os
import math
import time

from gmpy2 import gmpy2

class S:            # Scalar field
    N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
    N_2 = (N + 1) // 2          # 2**-1 mod N
    gmp_N = gmpy2.mpz(N)

    @staticmethod
    def add(a, b):
        return gmpy2.mod(gmpy2.add(a, b,), S.gmp_N)

    @staticmethod
    def mul(a, b):
        return gmpy2.mod(gmpy2.mul(a, b), S.gmp_N)


class F:        # Curve field
    P = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
    gmp_P = gmpy2.mpz(P)
    gmp_P4 = gmpy2.mpz((P + 1) // 4)

    @staticmethod
    def add(a, b):
        return gmpy2.mod(gmpy2.add(a, b), F.gmp_P)

    @staticmethod
    def mul(a, b):
        return gmpy2.mod(gmpy2.mul(a, b), F.gmp_P)

    @staticmethod
    def pow(b, e):
        return gmpy2.powmod(b, e, F.gmp_P)

    @staticmethod
    def sqrt(a):
        return gmpy2.powmod(a, F.gmp_P4, F.gmp_P)

    @staticmethod
    def inv(a):
        return gmpy2.invert(a, F.gmp_P)


class Point:            # Affine point
    def __init__(self, x, y, parity=-1):
        self.x = x
        self.y = y if parity == -1 else Point.calc_y(x, parity)

    @classmethod
    def uncompress(cls, s):
        parity, xh = int(s[:2], 16), s[2:]
        if parity not in [2, 3]:
            raise Exception("Expected parity 02 or 03")
        return Point(int(xh, 16), 0, parity % 2)

    @staticmethod
    def calc_y(x, parity):
        y = F.sqrt(F.add(F.pow(x, 3), 7))   # y = sqrt(x**3 + 7)
        return y if parity == y % 2 else F.P - y


class JPoint:           # Jacobian point
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

    def affine(self):
        z = F.inv(self.z)
        z2 = F.mul(z, z)
        return Point(F.mul(self.x, z2), F.mul(self.y, F.mul(z, z2)))

    def mul(self, n):
        if self.y == 0 or n == 0:
            return JPoint(0, 0, 1)

        if n == 1:
            return self

        if n < 0 or n >= S.N:
            return self.mul(n % S.N)

        if (n % 2) == 0:
            return self.mul(n // 2).double()

        return self.mul(n // 2).double().add(self)

    def double(self):
        if self.y == 0:
            return JPoint(0, 0, 0)

        y2 = F.mul(self.y, self.y)
        s = F.mul(4 * self.x, y2)
        n = F.mul(3 * self.x, self.x)

        x = F.add(F.mul(n, n), - 2 * s)
        return JPoint(x, F.add(F.mul(n, s - x), -F.mul(8 * y2, y2)), F.mul(2 * self.y, self.z))

    def add(self, q):
        if self.y == 0:
            return q
        if q.y == 0:
            return self

        qz2 = F.mul(q.z, q.z)
        pz2 = F.mul(self.z, self.z)
        u1 = F.mul(self.x, qz2)
        u2 = F.mul(q.x, pz2)
        s1 = F.mul(self.y, F.mul(q.z, qz2))
        s2 = F.mul(q.y, F.mul(self.z, pz2))

        if u1 == u2:
            if s1 != s2:
                return JPoint(0, 0, 1)
            return self.double()

        h = F.add(u2, -u1)
        r = F.add(s2, -s1)
        h2 = F.mul(h, h)
        h3 = F.mul(h, h2)
        u1h2 = F.mul(u1, h2)
        nx = F.add(F.mul(r, r), -F.add(h3, 2 * u1h2))
        ny = F.add(F.mul(r, F.add(u1h2, -nx)), -F.mul(s1, h3))
        nz = F.mul(h * self.z, q.z)

        return JPoint(nx, ny, nz)


class Group:
    G = Point(
        0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
        0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
    )

    @staticmethod
    def add(p: Point, q: Point):
        if p.x == q.x:
            raise Exception('Point adding with same X not supported')

        m = F.mul(F.add(p.y, -q.y), F.inv(F.add(p.x, -q.x)))
        x = F.add(F.add(F.mul(m, m), -p.x), -q.x)
        return Point(x, F.add(F.mul(m, F.add(q.x, -x)), -q.y))

    @classmethod
    def mul(cls, p: Point, k):
        return JPoint(p.x, p.y, 1).mul(k).affine()

    @classmethod
    def batch_add(cls, ga, gb):
        n = len(ga)
        d = [0] * n
        p = [0] * n
        z = 1
        for i in range(n):
            d[i] = F.add(ga[i].x, -gb[i].x)
            z = F.mul(z, d[i])
            p[i] = z

        t = F.inv(z)

        for i in range(n - 1, -1, -1):
            if i > 0:
                xi = F.mul(t, p[i - 1])
                t = F.mul(t, d[i])
            else:
                xi = t

            m = F.mul(F.add(ga[i].y, -gb[i].y), xi)
            ga[i].x = F.add(F.add(F.mul(m, m), -ga[i].x), -gb[i].x)
            ga[i].y = F.add(F.mul(m, F.add(gb[i].x, -ga[i].x)), -gb[i].y)


class TrueRandom:
    def __init__(self, max_value: int, num_bits: int, min_value: int = 0):
        self.upper_bound = max_value - min_value + 1
        self.min = min_value
        self.num_bits = num_bits
        self.domain = 2 ** num_bits
        self.num_bytes = math.ceil(num_bits / 8)
        self.shift = 8 * self.num_bytes - num_bits

    def get_next(self):
        random_bytes = os.urandom(self.num_bytes)

        # trim to num_bits
        random_int = int.from_bytes(random_bytes, byteorder='big') >> self.shift

        # normalize from domain range to target range
        sample = self.upper_bound * random_int // self.domain

        return sample + self.min


class Kangaroo:
    def __init__(self, k1: int, k2: int, dp: int, herd_size: int,
                 verify: bool = False):
        self.key_offset = k1

        b = k2 - k1 + 1
        # subtract k1 to search in [0, b) interval
        k2 -= k1
        k1 = 0

        self.k1 = k1
        self.k2 = k2
        self.b = b
        self.dp = dp
        self.herd_size = herd_size
        self.verify = verify

        self.m = herd_size + herd_size               # m/2 + m/2

        # compute optimal alpha for minimum expected total group operations
        # numJumps(a) = (b/4a + 4a/m**2) * m; minimal when a = m * sqrt(b)/4
        alpha = herd_size * math.sqrt(b) / 2

        self.jump_distances, self.jump_points, _ = self.build_jump_distances(alpha, with_points=True)
        self.n = len(self.jump_distances)

        # ensure we'll never stumble a kang onto a jump point (erase doubling branch)
        self.key_offset -= (2 ** (self.n + 10) + 1)
        self.k1 += (2 ** (self.n + 10) + 1)
        self.k2 += (2 ** (self.n + 10) + 1)

        self.alpha_real = sum(self.jump_distances) / self.n

        # adjust alpha to the actual average jump distance
        self.alpha_expected = alpha

        # (b/(4m * sqrt(b)/4) + 4(m * sqrt(b)/4)/m**2) * m == 2 sqrt(b)
        self.expected_total_jumps = 2 * math.sqrt(b)

        # set v to the "partition" size for a processor, and not a power of two
        # v = b // m - 1                          # (b/2) / (m/2)
        self.v = 37

        # DP overhead assuming we jump all herds at once, at every step
        self.expected_total_jumps += (1 << self.dp) * self.m
        self.avg_jumps_per_kang = self.expected_total_jumps / self.m

        self.hashmap = {}

        self.tames: list[Point | None] = [None] * herd_size
        self.t_dist = [0] * herd_size

        self.wilds: list[Point | None] = [None] * herd_size
        self.w_dist = [0] * herd_size

        self.P = self.P_neg = None
        self.num_created = 0

        self.batch_jp: list = [None] * herd_size     # buffer for batched group operations

    def solve(self, target_point: Point):
        # shift P to the left to bring DLP in search interval
        target_point = Group.add(target_point, Group.mul(Group.G, -self.key_offset))
        self.P = target_point

        self.num_created = 0
        self.hashmap.clear()
        k_cand, counter, tbl_size = self.kangaroo()

        failed = self.verify
        for i in range(len(k_cand)):
            if failed:
                q = Group.mul(Group.G, k_cand[i])
                if q.x == self.P.x and q.y == self.P.y:
                    failed = False
                    print(f'Private Key: {hex(S.add(k_cand[i], self.key_offset))}')
            k_cand[i] = S.add(k_cand[i], self.key_offset)

        if failed:
            raise Exception(f'Solution verify failed!\n{k_cand}')

        return k_cand, counter, tbl_size

    def create_kangaroo(self, kang_type, pos: int, v: int):
        k1, k2 = self.k1, self.k2

        if kang_type == 0:
            d = self.b // 2 + pos * v                       # b/2 + i * v

            self.t_dist[pos] = d
            self.tames[pos] = Group.mul(Group.G, k1 + d)
        else:
            d = pos * v                                     # 0 + i * v

            self.w_dist[pos] = d
            self.wilds[pos] = Group.add(self.P, Group.mul(Group.G, d))

    def check_col(self, kang_type, pos, dp_mask):
        k1, k2, hashmap = self.k1, self.k2, self.hashmap

        if kang_type == 0:
            herd, dist = self.tames, self.t_dist
        else:
            herd, dist = self.wilds, self.w_dist

        x = herd[pos].x     # shift >> 192 too expensive in Python

        if (x & dp_mask) == 0:
            item = hashmap.get(x)

            if item is not None:
                if item[0] != kang_type:
                    # collision
                    d_wild, d_tame = (item[1], dist[pos]) if kang_type == 0 else (dist[pos], item[1])
                    # k1 + tameDist == k + wildDist
                    return [
                        S.add(k1 + d_tame, -d_wild),        # k1 + t = k + w            (k > 0)
                    ], 0
                else:
                    # move forward
                    d = 7
                    dist[pos] += d
                    herd[pos] = Group.add(herd[pos], Group.mul(Group.G, d))

                    # this will recurse until a non-dead kangaroo is produced
                    k, created = self.check_col(kang_type, pos, dp_mask)
                    self.num_created += created
                    return k, 1 + created
            else:
                hashmap[x] = kang_type, dist[pos]

        return 0, 0

    @staticmethod
    def build_jump_distances(alpha, with_points=False):
        jump_points_fwd = None
        jump_points_bwd = None
        jump_distances = []

        # compute |A| as powers of two for all elements but the last
        # and adjust the last element so that avg(A) == alpha
        r = 0
        s = 0
        while True:
            jump_distance = 2 ** r
            s += jump_distance
            r += 1
            a = s / r
            if a < alpha:
                jump_distances.append(jump_distance)
            else:
                total = s - jump_distance
                last_dist = int(alpha * r - total)
                jump_distances.append(last_dist)
                break

        if with_points:
            jump_points_fwd, jump_points_bwd = Kangaroo.create_jump_points(jump_distances)

        return jump_distances, jump_points_fwd, jump_points_bwd

    @staticmethod
    def create_jump_points(jump_distances: list, base_p = Group.G):
        jump_points_fwd = []
        jump_points_bwd = []

        for dist in jump_distances:
            q = Group.mul(base_p, dist)
            jump_points_fwd.append(q)
            jump_points_bwd.append(Point(q.x, F.P - q.y))

        return jump_points_fwd, jump_points_bwd

    def kangaroo(self):
        k1, k2, dp, herd_size = self.k1, self.k2, self.dp, self.herd_size

        print(
            f'            kangaroos (m): {self.m}'
            f'\n      jump distances |A|: {self.n}'
            f'\n        avg jumps / kang: {self.avg_jumps_per_kang:.0f}'
            f'\n    expected total jumps: {self.expected_total_jumps:20.0f} {math.log2(self.expected_total_jumps):.2f} bits'
            f'\n avg ideal jump distance: {self.alpha_expected:20.0f} {math.log2(self.alpha_expected):.2f} bits'
            f'\n  avg real jump distance: {self.alpha_real:20.0f} {math.log2(self.alpha_real):.2f} bits'
        )

        batch_jp = self.batch_jp
        num_ops = 0
        dp_mask = (1 << dp) - 1

        for idx in range(herd_size):
            self.create_kangaroo(0, idx, self.v)
            self.create_kangaroo(1, idx, self.v)
            self.num_created += 2

        tames, t_dist = self.tames, self.t_dist
        wilds, w_dist = self.wilds, self.w_dist
        n = self.n

        start_time = time.time()
        last_p_time = 0

        while True:
            for idx in range(herd_size):
                k, born = self.check_col(0, idx, dp_mask)
                self.num_created += born
                if k:
                    return k, num_ops, len(self.hashmap)

                jump_idx = tames[idx].x % n
                # tames[idx] = Group.add(tames[idx], jump_points[d])        # un-batched addition
                batch_jp[idx] = self.jump_points[jump_idx]
                t_dist[idx] += self.jump_distances[jump_idx]

            Group.batch_add(tames, batch_jp)
            num_ops += len(tames)

            for idx in range(herd_size):
                k, born = self.check_col(1, idx, dp_mask)
                self.num_created += born
                if k:
                    return k, num_ops, len(self.hashmap)

                jump_idx = wilds[idx].x % n
                # wilds[idx] = Group.add(wilds[idx], jump_points[d])        # un-batched addition
                batch_jp[idx] = self.jump_points[jump_idx]
                w_dist[idx] += self.jump_distances[jump_idx]

            Group.batch_add(wilds, batch_jp)
            num_ops += len(wilds)

            total_time = time.time() - start_time
            if total_time - last_p_time > 2:
                last_p_time = total_time
                print(f'Ops: {num_ops} Table size: {len(self.hashmap)} Speed: {num_ops / total_time:.0f} ops/s')


def benchmark_kangaroo(bits: int, dp: int = 0, herd_size: int = 128, num_runs=100):
    k1, k2 = 1, 1 << bits       # k interval = [1, 2**n]
    total_group_add = 0         # group operations (e.g. P + Q)
    total_group_mul = 0         # point scalar ops (e.g. [k] * P)
    total_size = 0
    total_time = 0
    tr = TrueRandom(k2, 256, k1)
    kangaroo = Kangaroo(k1, k2, dp, herd_size, verify=True)

    for i in range(num_runs):
        pk = tr.get_next()
        print(f'Solve for {pk}')
        target_point = Group.mul(Group.G, pk)

        start_time = time.monotonic()
        k, ops, hashmap_size = kangaroo.solve(target_point)
        total_time += time.monotonic() - start_time

        found = False
        for k_cand in k:
            if k_cand == pk:
                found = True
                break

        if not found:
            raise Exception(f'BAD solution! {pk} not in {k}')

        total_group_mul += kangaroo.num_created
        total_group_add += ops
        total_size += hashmap_size

        ops_per_solve = total_group_add / (i + 1)
        dp_ovh = (1 << dp) * herd_size
        ops_minus_dp_overhead = ops_per_solve - dp_ovh
        print(f'[{i + 1}] AVG Ops: {ops_per_solve:.0f}'
              f' ({ops_minus_dp_overhead / math.sqrt(k2 - k1 + 1):.3f} * sqrt(b)'
              f' + (dp_ovh = 2**{math.log2(dp_ovh)}))'
              f' AVG Stored: {total_size / (i + 1):.0f}'
              f' AVG k_crt: {total_group_mul / (i + 1):.0f}'
              f' AVG Speed: {total_group_add / total_time:.0f} op/s'
              )


def run_puzzle(idx: int, pub_key, dp: int = 0, herd_size: int = 128):
    # puzzle #X has (X - 1) unknown bits
    k1 = 1 << (idx - 1)
    k2 = (k1 << 1) - 1
    target_point = Point.uncompress(pub_key)

    k = Kangaroo(k1, k2, dp, herd_size, verify=True)
    start_time = time.monotonic()
    _, ops, hashmap_size = k.solve(target_point)
    total_time = time.monotonic() - start_time

    print(f'Ops: {ops} Stored: {hashmap_size}'
          f'\nSpeed: {ops / total_time:.0f} ops/s'
          f'\nFinished in {total_time:.3} s'
          )


if __name__ == '__main__':
    # run_puzzle(32, '0209c58240e50e3ba3f833c82655e8725c037a2294e14cf5d73a5df8d56159de69',
    #            herd_size=512)

    # run_puzzle(40, '03a2efa402fd5268400c77c20e574ba86409ededee7c4020e4b9f0edbee53de0d4',
    #            herd_size=512, dp=4)

    run_puzzle(48, '0291bee5cf4b14c291c650732faa166040e4c18a14731f9a930c1e87d3ec12debb',
               herd_size=1024, dp=6)

    # benchmark_kangaroo(32, dp=4, herd_size=512, num_runs=1000)

48 bits finished 3 times faster than expected. Oh well.
Zero randomness used (the only purpose of TrueRandom is to generate random private keys to benchmark).

Code:
Private Key: 0xade6d7ce3b9b
Ops: 8690688 Stored: 135855
Speed: 396380 ops/s
Finished in 21.9 s
Pages:
Jump to: