read.cash is a platform where you could earn money (total earned by users so far: $ 841,632.64).
You could get tips for writing articles and comments, which are paid in Bitcoin Cash (BCH) cryptocurrency,
which can be spent on the Internet or converted to your local money.

When I first wrote Puzzhole I was confident that it would get solved, but I had no idea how long until it would get solved, especially considering I wasn't able to fund it with a large prize pool. Immediately after it went live I noticed that some users began to say that it might not be solvable and I was beginning to get worried.

But then Reddit user -johoe posted that he had solved it at the same time that half of the funds of the wallet were removed! Congratulations! You guys probably already know him from his well known tool: Johoe's Bitcoin Mempool Size Statistics.

Also nice of him to leave half the prize pool for future solvers. Classy! 🍷(Upon request he later swept the remaining before I published this article)

As you can see the image is composed of many different symbols. If you break them down you'll find there are 8 symbols in total:

Each symbol is about how many segments (or "holes") there are. A zero is such because it has no segments or holes in it, a one is such because it has one hole in the triangle, two because it has two holes in the figure eight and so on. When I asked Johoe about how he discovered this and the process of figuring it out he said:

Counting gave me 8 different symbols. I first tried to count strokes but didn't work out (not even with counting loops as two strokes; no 7 strokes), then counting the regions worked out. Only after I solved it, I noticed the puzzhole title.

Once you understand that system you can take all of the symbols and put them into some kind of variable:

Lastly you can turn all of those bits into a private key:

key_bytes = []
for n in range(0, len(bits), 8):
word = int(bits[n:n+7], 2)
key_bytes.append(word)
print(bytearray(key_bytes).decode('ascii', 'ignore'))

This will leave you with the private key in WIF format:

KwEezDy4Hjr...

And you're done! I asked Johoe to share his script he used to solve the puzzle and he graciously did. It's written in Perl and a bit of a brain twister, but here it is:

No, it was a red herring. It simply looked better with a random rotation applied to each symbol. Here is what the puzzle looks like without the rotation:

Here is the original script that generates the puzzle image from a private key:

from math import sqrt, ceil
from PIL import Image
from random import randrange
import sys
private_key = 'REDACTED WIF key'
key_bytes = private_key.encode('ascii')
bits = bin(int.from_bytes(key_bytes, byteorder='big'))
if bits[0:2] != "0b":
print("Expected 0b at start of bit string")
sys.exit(1)
bits = bits[2:]
print("bits", bits)
total_bytes = len(key_bytes)
total_bits = len(bits)
total_glyphs = ceil(total_bits / 3)
grid_size = ceil(sqrt(total_glyphs))
print("key is", total_bytes, "bytes")
print("key is", total_bits, "bits")
print("key has", total_glyphs, "glyphs")
print("grid size is", grid_size)
# Pad bits
bits = bits + "000"
col = 0
row = 0
width = grid_size * 100
height = grid_size * 100
img = Image.new(mode="RGBA", size = (width, height), color='#fff')
glyphs = {}
glyph_encoding = ""
for i in range(0, 8):
glyphs[i] = Image.open("puzzhole_images/" + str(i) + ".png")
print("bits", bits)
col = 0
row = 0
for i in range(0, total_bits, 3):
n = int(bits[i + 2]) | (int(bits[i + 1]) << 1) | (int(bits[i]) << 2)
glyph_encoding += str(n)
g = glyphs[n]
g = g.rotate(randrange(0, 360))
img.paste(g, (col * 100, row * 100))
col += 1
if col >= grid_size:
col = 0
row += 1
print("glphs", glyph_encoding)
img.save('puzzhole_tmp.png')
# img.show()

And here is the test solver Python script:

glyphs = "4556..."
bits = ""
for n in glyphs:
word = bin(int(n))[2:].rjust(3, '0')
bits += word
bits += ""
print("bits", bits)
# If testing bits just paste them here and uncomment
#bits = "10010..."
key_bytes = []
for n in range(0, len(bits), 8):
word = int(bits[n:n+7], 2)
key_bytes.append(word)
print(bytearray(key_bytes).decode('ascii', 'ignore'))