Отрицательный опыт придает лицу умное выражение.
Этот пост, в первую очередь, для тех кто захочет попытаться сделать тоже самое. Так как я не криптограф и
Идея
По идее, если есть алгоритм преобразования одних данных в другие, то обязательно есть алгоритм чтобы обратно получить исходные данные.
В случае хеширования это должно быть «вычислительно неосуществимо», но у 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/
Добавить комментарий