Как я хотел обхитрить биткойн, но ничего не вышло

от автора

Отрицательный опыт придает лицу умное выражение.

Этот пост, в первую очередь, для тех кто захочет попытаться сделать тоже самое. Так как я не криптограф и не биткоин-фан, буду благодарен за полезные комментарии от профи.

Идея

По идее, если есть алгоритм преобразования одних данных в другие, то обязательно есть алгоритм чтобы обратно получить исходные данные.
В случае хеширования это должно быть «вычислительно неосуществимо», но у SHA2 вроде бы нашли кое какие слабые места. Это толкнуло меня на эту авантюру. А что если сделать такой майнер, который будет более лучше искать Nonce (соль) вместо тупого перебора.
Чтобы не напрягать свои нейроны вопросом создания этого алгоритма я решил делегировать этот процесс искусственным нейронам. Мне казалось что может быть проще: скормить нейросети выборку для обучения и на выходе получить готовый алгоритм.

Итак, наша нейросеть должна, на входе получать некие входные данные и на выходе давать тот самый Nonce (далее просто «соль») при использовании которого будет получен такой хеш нового блока, который будет удовлетворять условию сложности. Ну то есть иметь определенное количество нулей в начале.
Вот так вот просто, без нудного перебора, без ASIC, на обычном процессоре сразу получить нужное значение Nonce, ну разве не заманчиво?

Чтобы не перегружать мозг, можно перейти к последнему абзацу «Вывод».

Выборка для обучения

Любой желающий может получить все сгенерированные ранее блоки вместе с их солью. Они все автоматически скачиваются при каждой установке кошелька. По сути это и есть наша выборка: данные для генерации блока — это вход для нейросети, соль этого блока — выход.
Данные для генерации блока это определенная последовательность байт содержащая: версию протокола, хеш предыдущего блока, корень транзакций нового блока, время, и еще какую то фигню непроидентифицированную последовательность бит.
Наш биткойн-клиент установленный на компе, любезно готов предоставить всю эту информацию.
Коннектимся к нему:

http_client = pyjsonrpc.HttpClient(     url="http://localhost:8332",     username="bitcoinrpc",     password="***" ) 

Получаем номер последнего блока:

blocks_count = http_client.call("getblockcount") 

Создаем два файла: для тестирования сети и для обучения. Первые 100 записей идут в файл тестирования и следующие 100 000 записей для обучения:

f = open('test_data_100.txt', 'w') for i in xrange(blocks_count-100, blocks_count-1):     print i     hash = http_client.call("getblockhash", i)     block = http_client.call("getblock", hash)     workdata = getWorkData(int(block['version']), block['previousblockhash'], block['merkleroot'], int(block['time']), block['bits'])     f.write("%s\t%s\n" % (workdata, block['nonce'])) f.close()  f = open('train_data_100000.txt', 'w') for i in xrange(blocks_count-100000, blocks_count-101):     print i     hash = http_client.call("getblockhash", i)     block = http_client.call("getblock", hash)     workdata = getWorkData(int(block['version']), block['previousblockhash'], block['merkleroot'], int(block['time']), block['bits'])     if int(block['version']) == 2:         f.write("%s\t%s\n" % (workdata, block['nonce']))  f.close() 

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

Если с солью все просто — она хранится в блоке в готовом виде, то с входными данными сложнее, их надо сформировать:

def getWorkData(version, prevhash, merkleroot, time, bits):     header_hex = (         struct.pack("<I", version).encode('hex') +           be2le(prevhash) +           be2le(merkleroot) +           struct.pack("<I", time).encode('hex') +           be2le(bits))     return header_hex  def be2le(str):     targetbin = str.decode('hex')     targetbin = targetbin[::-1]        # byte-swap and dword-swap     targetbin_str = targetbin.encode('hex')     return targetbin_str 

Проверить, что мы правильно генерим входные данные можно вот так:

hash = http_client.call("getblockhash", blocks_count-3) block = http_client.call("getblock", hash) nonce = struct.pack("<I", block['nonce']).encode('hex') print blockhash(int(block['version']), block['previousblockhash'], block['merkleroot'], int(block['time']), block['bits'], nonce) print hash  def blockhash(version, prevhash, merkleroot, time, bits, nonce):     header_hex = (getWorkData(version, prevhash, merkleroot, time, bits) + nonce)     header_bin = header_hex.decode('hex')     hash = hashlib.sha256(hashlib.sha256(header_bin).digest()).digest()     hash.encode('hex_codec')     return hash[::-1].encode('hex_codec') 

Итак, теперь мы имеем два файла с примерно таким содержимым:

 0200000036ae9e130ad5bc8316dc0a61c9f4241a9f76ded2cf1a8f2300000000000000007bb063953fac3fb84ca67fa140ab96ca41313b6f21baa91115e1aced8cfe0b7ef5772753b1020119	1686062343 02000000d9c6e556860e9ec861be1cbbcf0e91cdf73feaa68ac7afaa0000000000000000b2f5ee99fc5413cf626eee67076f643974f2129887760f388a0fe305827bf924ac782753b1020119	52342214 0200000009914b4bf00456d6031bd1a51b2135f58cdbc9525525c7f6000000000000000045a1b6467c3386b2f5750bd95c19837037e44ee17f64c4970dc6c28341eae531c37a2753b1020119	2615696700 02000000cab2c1f838026f66af4c12b9e4b12bb75d2c9cfa986280ac00000000000000001c6e3a48ab5d301b73c20be56dc36fe6e8efaf57edd29ecba4040eb4064b2ec11c812753b1020119	2392551844 ... 

Первая часть строки вход нейросети, вторая — выход.

Обучение и тестирование

Я подбирал разные варианты конфигурации сети и даже пробовал разные типы слоев но картина примерно такая или хуже.

Я решил создавать сеть таким образом чтобы она уже изначально была предрасположена к нашей тестовой выборке. На практике это ни на что не повлияло:

fname = 'networks/network.xml' test_ds = getDataSet('test_data_100.txt') print "Creating file %s" % fname best_res = 16 n = '' while True:     n1 = buildNetwork(IN, int(round(IN/3)), int(round(IN/3)), int(round(IN/3)), int(round(IN/3)), int(round(IN/3)), OUT, bias=False, recurrent=True, fast=True)     total_errors, bingo = test_network(n1) # протестируем свежую сеть на профпригодность     print "%.1f %s" % (total_errors, bingo)     if total_errors < best_res:         n = n1         best_res = total_errors         break  ds = getDataSet('train_data_100000.txt') t = BackpropTrainer(n, ds)  for i in xrange(1, 2000):     a = datetime.datetime.now()      train_res = t.train()     NetworkWriter.writeToFile(n, fname)      b = datetime.datetime.now()     test_res = t.testOnData(dataset=test_ds)      total_errors, bingo = test_network(n)      if bingo == 0:         bingo = ""     else:         bingo = "(" + str(bingo) + ")"     print( "%.3f %.3f %.1f %s time:%d i:%s %s" % (train_res, test_res, total_errors, bingo, (b-a).seconds/60, i, b))     time.sleep(1) # кулер гудит 

Тестировал я сразу после каждой итерации обучения, чтобы сразу видеть как идет обучение и прервать процесс если он не удовлетворительный. Так как выборка большая, то одна итерация обучения длилась 10-20 минут, это долго конечно.

Тестировал сеть я просто:

def test_network(n):     total_errors = []     bingo = 0     for inpt, target in test_ds:         res = n.activate(inpt)         res = [-1 if x < 0 else 1 for x in res]         errors = 0         for j in xrange(0, OUT-1):             if res[j] != target[j]:                 errors += 1         total_errors.append(errors)         if errors == 0: # ага, размечтался!             bingo += 1      return numpy.median(total_errors), bingo 

Чтобы передать данные нейросети их конечно надо преобразовать:

IN = 602 OUT = 34  def bin(s):     return str(s) if s<=1 else bin(s>>1) + str(s&1)   def add_zeros(inp, num):     if len(inp) < num:         for i in range(len(inp), num):             inp.insert(0, -1)     return inp   def bin2bits(str, zeros, down_value):     str = add_zeros([(down_value if x == '0' else 1) for x in str], zeros)     return str   def line2Bits(line):     data = line.strip().split('\t')     workdata = int(data[0], 16)     workdata = bin(workdata)     workdata = bin2bits(workdata, IN, 0)     nonce = int(data[1])     nonce = bin(nonce)     nonce = bin2bits(nonce, OUT, -1)     return [workdata, nonce]  def getDataSet(filename):     tf = open('data/' + filename, 'r')     ds = SupervisedDataSet(IN, OUT)      for line in tf:         [workdata, nonce] = line2Bits(line)         ds.addSample(workdata, nonce)     tf.close()     print filename + ' loaded success.'     return ds 

Вывод

ссылка на оригинал статьи http://habrahabr.ru/post/217149/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *