Совсем недавно наткнулся в одном из телеграмм каналов на новость, о том, что один художник по имени Andy Bauch спрятал в своих картинах ключ к биткойнам.

Меня привлекают переплетения искусства и технологии, поэтому я заинтересовался и решил попробовать свои силы.

Произведения Энди представляют собой рисунки созданные из разноцветных частичек конструктора лего.

Вот одна из работ, в которой спрятаны $20 по курсу на апрель 2016 года:

Невооруженным взглядом заметен повторяющийся шаблон. Остается разобраться что он значит.

С помощью gimp сделал картинку понятнее для анализа:

Gimp processed pattern

Итак, разработаем небольшой скрипт из подручных средств. А под рукой у меня оказался старый добрый PHP и Imagick =) Цель скрипта - получить последовательность бит в соответствии с цветами пикселей картины.

$originFile = storage_path("20_BITCOIN_SM.jpg");
$origin = new \Imagick($originFile);


$len = $request->get('len', 210);

$total = 48*48;

$rW = $len * 2 + 10;
$rH = round($total / $len) * 2 + 10;

$test = new \Imagick();
$test->newImage($rW, $rH, "black");
$test->setImageFormat("png");

$w = 18.9; // ширина пикселя
$h = 18.9; // высота пикселя

$draw = new \ImagickDraw();
$draw->setStrokeColor(new \ImagickPixel('black'));
$draw->setFillColor(new \ImagickPixel('white'));
for ($y = 0; $y <= 47; $y++) {
    for ($x = 0; $x <= 47; $x++) {
	$destin = $origin->getImageRegion($w, $h, 46 + $w * $x, 50 + $h * $y);
	$destin->scaleimage(1, 1);
	if (!$pixels = $destin->getimagehistogram()) {
	    return null;
	}
	$pixels = reset($pixels);
	if (array_sum($pixels->getcolor()) > 256) {
	    $nx = 1 + ($i % $len);
	    $ny = 1 + floor($i / $len);
	    $draw->point($nx, $ny);
	}
    }
}
$test->drawImage($draw);
return response($test->getImageBlob(), 200, ['Content-type' => 'image/png']);

Перебор параметра len, как и ожидалось, дал свои плоды:

bar pattern

Сканер штрих-кодов никак не реагирует на последовательность полосок. Идем дальше.

Теперь вместо отрисовки картинки, превратим белые пиксели в 1, а черные в 0;

echo (array_sum($pixels->getcolor()) > 256) ? 1 : 0;

Получаем 210 бит, что, конечно, мало для приватного ключа:

101001101101000111001100101010000011110011110111001101110111001011010011010011110001111011011100100110110111001110101001101111110010010110001100110110010011001100110100011000110011101000011100011011000011000100

Нахожу делители числа 210: 2, 3, 5, 6, 7, 10, 14, 15, 21, 30, 35, 42, 70, 105

210 на 8 не делиться, значит это не набор байт. Представить в виде изображения матрицу 14x15. На QR код не похоже. А вот на матрице 7x30 проглядывается закономерность - слева больше черных пикселей. Это похоже на битовые коды символов в диапазоне 0-127.

key matrix

А что если перед каждой группой байт подставить 0?

$bits = "101001101101000111001100101010000011110011110111001101110111001011010011010011110001111011011100100110110111001110101001101111110010010110001100110110010011001100110100011000110011101000011100011011000011000100";
echo "0" . join(" 0", str_split($bits, 7));

Получили:

01010011 00110100 00111001 01001010 01000001 01110011 01101110 00110111 00111001 00110100 01101001 01110001 01110110 01110010 00110110 01110011 01010100 01101111 01100100 01011000 01100110 01100100 01100110 00110100 00110001 01001110 01000011 01000110 01100001 01000100

А теперь этот набор байт превратим в текст удобным инструментом. Вуаля! Получили вполне адекватную строку:

S49JAsn794iqvr6sTodXfdf41NCFaD

Очевидно, это не приватный ключ, и вполне возможно что это Mini private key. Что легко проверяется генератором ключей.

Получаю такой адрес:

1NmxAV1ze28U4Uuqg2fH1JTB8NtWKvTyhM

Проверяю его баланс и огорчаюсь, что я опоздал на пол дня и кто-то разгадал загадку вперед меня и перевел себе 0.04678122 BTC, что почти 400 баксов по текущему курсу.

https://blockchain.info/address/1NmxAV1ze28U4Uuqg2fH1JTB8NtWKvTyhM

Баланс на Bitcoin Cash по этому адресу так же обращен в 0 :(

Ну что ж, польза от решения загадки осталась - я нашел интересный, полезный и удобный инструмент: The Cyber Swiss Army Knife

Это была бессонная ночь =)