Solving Decred “Batman heads to Gotham” puzzle

Narcélio Filho
6 min readFeb 5, 2017

This was the second Decred’s puzzle, made by Decred team, led by

. A link to the challenge was posted on Decred project’s twitter account:

On that page, there was a large GIF file:

https://dcrstats.com/images/0n0urw4y.gif

As previous puzzles, this GIF has a zip inside it, hiding another file:

$ unzip 0n0urw4y.gif 
Archive: 0n0urw4y.gif
warning [0n0urw4y.gif]: 32463159 extra bytes at beginning or within zipfile
(attempting to process anyway)
inflating: 533d.7z
creating: __MACOSX/
inflating: __MACOSX/._533d.7z

But this 533d.7z (“seed” in leet speak) file is password protected:

$ 7z x 533d.7z7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02
Scanning the drive for archives:
1 file, 726 bytes (1 KiB)
Extracting archive: 533d.7z
--
Path = 533d.7z
Type = 7z
Physical Size = 726
Headers Size = 134
Method = LZMA:16 7zAES
Solid = -
Blocks = 1
Enter password (will not be echoed):
ERROR: Data Error in encrypted file. Wrong password? : seed.rtf

Sub items Errors: 1
Archives with Errors: 1Sub items Errors

A “seed”, of a cryptocurrency, is a bunch of random words that can create or restore a wallet. It is like a very strong password. So, this file named seed.rtf may contain those words.

Now we have to discover the 7zip file’s password. But what information do we have?

Let’s go back and take a look at that GIF:

See the Batman signal blinking on a strange pattern? Let’s extract all frames from this GIF. You can use a lot of tools to do it, even some online. I’ll use one from Ubuntu tools, called gifsicle:

$ mkdir frames
$ cd frames
$ gifsicle -e ../0n0urw4y.gif
gifsicle:../0n0urw4y.gif: warning: trailing garbage after GIF ignored
$ ls -1
0n0urw4y.gif.000
0n0urw4y.gif.001
0n0urw4y.gif.002
...
0n0urw4y.gif.334
0n0urw4y.gif.335
0n0urw4y.gif.336

There were 337 frames in this GIF:

Some of them has a bat-signal on the left-upper area and some doesn’t have. Many players thought that it could be a morse signal. But morse code information needs three things: dots, dashes and spaces. So, it couldn’t be a morse signal, as there are only two types of data: signal and space.

This must be binary data!

You can convert all the frames manually, turning those with bat-signal to 1 and, those without, to0. As I’m a very lazy guy, I’ll just tell my computer to do it for me:

$ for gif in frames/* ; do 
convert $gif \
-crop 50x50+90+60 -scale 1x1\! \
-colorspace Gray -format "%[mean]\n" info:- ;
done | awk '{ printf int($0 > 25000); }' > bins.txt

This code tells my computer to take a small square on left-upper region, downscale to one pixel, write brightness information, and if it is bright the bit is 1 else is 0. File bins.txt should have this binary data:

0000000101010101010101010111101110101011000010011011101100110000100011011100100111000010011000110001011101111000000101001011110011110111100010001010000100011101101111110011001011001010000110000111101100110001110010001011100011001001010100110100110110111100000111111110000100110010011111111011000001010111000111110000000000000000000000000

There is it. 337 bits of information. What it would mean?

Well, 337 isn’t a good number to deal with it. It’s kinda clumsy, because it’s a prime, it doesn’t have any factors. It can’t be divided in bytes (groups of 8 bits) or group of any size. So, there must be some slack bits somewhere.

Let’s get back to the frames. There are some frames at start and end of the animation that has the Decred logo and no bat-signal. Are they part of the binary data information or just noise? There are 6 at start and 7 at end. If we remove then, the number of bits goes down to 324, and it is a excellent number to work with, because it has many factors! So, binary data became:

010101010101010101011110111010101100001001101110110011000010001101110010011100001001100011000101110111100000010100101111001111011110001000101000010001110110111111001100101100101000011000011110110011000111001000101110001100100101010011010011011011110000011111111000010011001001111111101100000101011100011111000000000000000000

But it still cannot be divided by 8 (would remain 4 bits). So, we need to divide it by which number?

Take a look at the zeros at the end. There are 18 zeros there. And you know the relation between 18 and 324? 324 = 18². Yes, this binary information is a square!

Let’s reorganize it in a perfect 18x18 matrix:

010101010101010101
011110111010101100
001001101110110011
000010001101110010
011100001001100011
000101110111100000
010100101111001111
011110001000101000
010001110110111111
001100101100101000
011000011110110011
000111001000101110
001100100101010011
010011011011110000
011111111000010011
001001111111101100
000101011100011111
000000000000000000

Can you see the patterns emerging? Take a look again:

010101010101010101
011110111010101100
001001101110110011
000010001101110010
011100001001100011
000101110111100000
010100101111001111
011110001000101000
010001110110111111
001100101100101000
011000011110110011
000111001000101110
001100100101010011
010011011011110000
011111111000010011
001001111111101100
000101011100011111
000000000000000000

Would it be just a coincidence? Nope! This is a Data Matrix, a two-dimensional barcode consisting of black and white pixels. You must have seen it before, on a sticker on some electronic device.

So, let’s create a image and scan it!

For this step you can open any image tool and create the pixels one by one, or maybe just print it and cover the number with some pen but, yes, I’m lazy and my PC is fast:

Python is the best programming language ever.

This program (or your work) would create an image like this:

Now, just scan it with some app on your smartphone (or use a online tool like this excellent decoder) and you will get this:

Finally, we have some plaintext information!

37.790029, -122.40084

This is a GPS coordinate from San Francisco. Ok, now we know where Decred is going to! This is a GPS coordinate to Coinbase headquarters.

AND, it is also the password for 533d.7z file!

$ 7z x 533d.7z7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02
Scanning the drive for archives:
1 file, 726 bytes (1 KiB)
Extracting archive: 533d.7z
--
Path = 533d.7z
Type = 7z
Physical Size = 726
Headers Size = 134
Method = LZMA:16 7zAES
Solid = -
Blocks = 1
Enter password (will not be echoed):
Everything is Ok
Size: 1088
Compressed: 726

Nice! Well, so, inside it must be the seed for the wallet with the prize, right?

Wrong.

seed.rtf

There are 33 words, obviously a wallet seed. But it is ciphered. I first tried a monoalphabetic cipher, rotating letters by one, but it doesn’t work. So, it must be a harder cipher. I think it is Vigenère Cipher. Lets use DCODE, a great online decoder to break it:

Well, it need more info to decrypt it. One know atack to Vigenère Cipher is the Known-plaintext Attack, when the cryptanalyst knows some piece of information from the original text.

But does we know any of those words? Yes! We know all of them! They are listed in this Decred Wallet’s source file:

There are 512 words on that list. But which one can we pick? Take a look again at the highlighted ciphered word Biig. It is a 4 letter word, beginning with a capitalized one. Looking through the mnemonics wordlist, this word could be Ohio. Let’s try it:

Vigenère code broke! The password is COINBASE. Lets decrypt it:

Finally, the wallet seed! Let’s try it on https://wallet.decred.org/:

And there’s the prize!

--

--