Как технический документ декодируется из блокчейна (Tx с ~ 1000x m из n мультиподписных выходов)

Технический документ, по- видимому, закодирован как 54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713 , что представляет собой m из n multisig Tx с 947 выходами (чуть меньше предела scriptsig в ​​20 КБ!).

Используя Blocktrail Python SDK , я получаю список выходных данных в виде шестнадцатеричного кода, используя следующий код Python (2.7) (примечание: параметры APIKEY , APISECRET доступны, если требуется, на сайте www.blocktrail.com ):

from blocktrail import APIClient
bt_client = APIClient(APIKEY, APISECRET, network='BTC')
txnObj = bt_client.transaction('54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713')
hashes = [(t['script_hex']) for t in (txnObj)['outputs']]   

Полученный список доступен здесь в полном объеме и, по сути, состоит из всех Txns pay-to-pubkey-script. Выдержка:

[u'5141e4cf0200067daf13255044462d312e340a25c3a4c3bcc3b6c39f0a322030206f626a0a3c3c2f4c656e6774682033203020522f46696c7465722f466c617465446541636f64653e3e0a73747265616d0a789cad5c4b8b24b911becfafa8b3a1da292925654253d0d55373f06d61c007e39bbd061f0cde8bffbe25c55b5266f61ab3905d419ba54728e28bb76a963777fbcfb77fdf96db7d291f93f3e599f7fafcedefb73fffe1f6aff665fdefb77f7c7bfefce6c2fa166e695bdfd6dbcfbfddfef8c3dd5cf953ae',
.....
u'514130206e200a30303030313832353430203030303030206e200a747261696c65720a3c3c2f53697a652036382f526f6f74203636203020520a2f496e666f20363720413020520a2f4944205b203c43413142304134344244353432343533424546393138464643443436444330343e0a3c4341314230413434424435343234353342454641393138464643443436444330343e205d0a2f446f63436865636b73756d202f36463732454137353134444641443233464142434337413535303032314146370a3e53ae',
 u'51213e0a7374617274787265660a3138323732370a2525454f460a000000000000000051ae',
 u'76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac',
 u'76a914031c79236ff3017496cf8d9a883f494458f245f288ac']

ВОПРОС: Как этот массив шестнадцатеричных данных преобразуется в биткойн.pdf ? Конкретные ответы в формате Python будут оценены!

Я должен добавить его 945x *? из 3*(?) txns и 2x выходов paytopublickey
scriptsig limit of 20kB!Я знал об ограничении в 10 КБ, введенном в EvalScript, но о каком ограничении в 20 КБ вы говорите?
@NickODell - это не предел для сценариев 20 КБ?

Ответы (3)

По сути, это забавная маленькая головоломка на блокчейне. Во-первых, вам нужно немного узнать о pdf-файлах и их структуре, которые вы можете найти здесь .

Во-вторых, вы заметите в разделе 3.4.1, что все pdf-файлы начинаются с этой строки:

%PDF-

В шестнадцатеричном формате, то есть 255044462d. И действительно, это в самом первом выводе самого первого голого мультиподписного ключа:

<e4cf0200><067daf13>**255044462d**312e340a25c3a4c3bcc3b6c39f0a322030206f626a0a3c3c2f4c656e6774682033203020522f46696c7465722f466c6174654465

Я не понял, для чего предназначены первые 8 байтов (♦ edit : e4cf0200067daf13 = 2x 4byte little Endian "checksums", see @WizardOfOzzie comment below), но остальные голые мультиподписные ключи (все между 1 и 3 OP_CHECKMULTISIG в каждом выводе - обратите внимание, что последний - 1 из 1, так что это 1 OP_CHECKMULTISIG) это фрагменты данных для pdf и они в порядке. Если вы можете поместить все шестнадцатеричные цифры голых мультиподписных ключей в один файл (без пробелов) с именем «fromblockchain.hex», вы можете запустить эту очень простую программу для извлечения pdf:

contents = open('fromblockchain.hex').read()
bytes = contents[16:].decode('hex')
f = open("bitcoin.pdf")
f.write(bytes)
f.close()

Это должно создать биткойн.pdf, который является настоящим техническим документом по сатоши. Я проверил это, и это действительно технический документ. Приятно знать, что это буквально в блокчейне.

В качестве альтернативы, если на вашем компьютере запущен биткойн, вы можете запустить этот скрипт python, чтобы получить технический документ биткойнов:

import subprocess

# raw = full hex of raw Tx using Bitcoin-cli
raw = subprocess.check_output(["bitcoin-cli", "getrawtransaction", "54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713"])

outputs = raw.split("0100000000000000")

pdf = ""
for output in outputs[1:-2]:
    # there are 3 65-byte parts in this that we need
    cur = 6
    pdf += output[cur:cur+130].decode('hex')
    cur += 132
    pdf += output[cur:cur+130].decode('hex')
    cur += 132
    pdf += output[cur:cur+130].decode('hex')

pdf += outputs[-2][6:-4].decode("hex")
f = open("bitcoin.pdf", "wb")
f.write(pdf[8:-8])
f.close()

Наконец проверка контрольной суммы

sha256sum bitcoin.pdf
b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553  bitcoin.pdf
Ах ха! Еще один фантастический ответ! Я пытался заставить работать скрипт, подобный приведенному выше, и не мог проанализировать шестнадцатеричный код после 0x5141и между 0x41s. Что случилось со стандартными двумя последними выходами 76a914____88ac?
Мне показалось, что человек тратил 1 сатоши на каждый выход и хотел вернуть сдачу за последний. Насчет предпоследнего не уверен.
Возможно, это двойное назначение: позволяет собирать выходные данные, а длина данных не может быть дополнена, поскольку 20 байт x2 % 8, тогда как сделать это с 65 байтами намного сложнее (на самом деле 65x3)
Вы хотели отредактировать нижний скрипт, чтобы добавить вывод на outputs[-2]? Это необходимо сценарию после цикла for: pdf += outputs[-2][6:-4].decode('hex'). Также необходимо использовать openopen("bitcoin.pdf", 'wb') (двоичный режим). Мне нужно будет изучить мультиподпись больше b/c. Я до сих пор не понимаю, почему остальные мультиподписи подталкивают байты 0x41, а последние подталкивают только байты 0x21.
Можете ли вы отредактировать свой фантастический ответ, чтобы также показать обратную функциональность (т.е. bitcoin.pdf=> rawhextx) с помощью Python? Форумы / сабреддиты, посвященные Python, с трудом понимают аспект Биткойн. РЕДАКТИРОВАТЬ: да, я знаю, что это нестандартный Tx; это только для интеллектуального любопытства
Re: 0xe4cf0200067daf13( "Я так и не понял, для чего нужны первые 8 байт" ). На самом деле это 2x 4-байтовые «контрольные суммы». В частности, маленькие шестнадцатеричные представления Endian: <total pdf size> <crc32 for pdf>ie 184292 330267910;)
Хороший улов @WizardOfOzzie. В ближайшее время я постараюсь написать что-нибудь в противоположном направлении (любой pdf, а не только технический документ по биткойнам).
этот код - gist.github.com/shirriff/… - я полагаю, что он использовался. Проблема в том, что это зависит от фактических UTXO и подсчитывает значения, проверяя оба, тогда как я просто пытаюсь создать необработанный неподписанный Tx и подписать его (т.е. нет необходимости в RPC Bitcoincore, я предоставлю входные данные, такие как txid, VOUT так далее)
Я сделал бинарное сравнение с предыдущей версией, bitcoincore.orgи она, похоже, отличается. Я предполагаю, что это какая-то нерелевантная вещь метаданных.
Кроме того, getrawtransactionтеперь принимает необязательный blockhashаргумент. Так что это устраняет необходимость -txindexздесь.
@WizardOfOzzie Я провел дополнительный анализ этого скрипта загрузки по адресу: bitcoin.stackexchange.com/a/105574/21282 и согласен с тем, что он почти наверняка использовался.

Это команда bash, которая также может дать вам файл:

bitcoin-cli getrawtransaction 54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713 0 00000000000000ecbbff6bafb7efa2f7df05b227d5c73dca8f2635af32a2e949  | sed 's/0100000000000000/\n/g' | tail -n +2 | cut -c7-136,139-268,271-400 | tr -d '\n' | cut -c17-368600 | xxd -p -r > bitcoin.pdf

Ему не нужен -txindex, но нужен полный узел, а не обрезанный. Я полагаю, что можно сделать вариант, gettxoutкоторый работает с обрезанными узлами, но это остается в качестве упражнения для читателя.

Упражнение выполнил @jb55 :-) seq 0 947 | (while read -r n; do bitcoin-cli gettxout 54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713 $n | jq -r '.scriptPubKey.asm' | awk '{ print $2 $3 $4 }'; done) | tr -d '\n' | cut -c 17-368600 | xxd -r -p > bitcoin.pdfИсточник: bitcoinhackers.org/@jb55/105595146491662406

В PDF-файле используются скрипты загрузки/загрузки Python, присутствующие в самой цепочке биткойнов.

Эти скрипты присутствуют по адресу:

Это мои разветвления Gists Кена Ширриффа, которые, предположительно, нашли их во время поиска: http://www.righto.com/2014/02/ascii-bernanke-wikileaks-photographs.html

Оба скрипта имеют заголовок с копирайтом:

#
# File downloader
# Requires git://github.com/jgarzik/python-bitcoinrpc.git
#
# (c) 2013 Satoshi Nakamoto All Rights Reserved
#
# UNAUTHORIZED DUPLICATION AND/OR USAGE OF THIS PROGRAM IS PROHIBITED BY US AND INTERNATIONAL COPYRIGHT LAW

что, вероятно, является шуткой и, на мой взгляд, написано не Сатоши.

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

После загрузки загрузчика вручную мне удалось запустить его, чтобы извлечь побайтовое соответствие PDF-файлу, представленному на https://web.archive.org/web/20210418161957/https://bitcoin.org/bitcoin.pdf . (sha256 == b1674191a88ec5cdd733e4240a81803105dc412d6c6708d53ab94fc248f4f553).

Во-первых, вам нужно локально запустить основной сервер Биткойн. Предположим, что ваш .bitcon/bitoin.confсодержит:

rpcuser=asdf
rpcpassword=qwer
server=1
txindex=1

Затем я запускаю:

git clone git://github.com/jgarzik/python-bitcoinrpc.git
git -C python-bitcoinrpc checkout cdf43b41f982b4f811cd4ebfbc787ab2abf5c94a
wget https://gist.githubusercontent.com/shirriff/64f48fa09a61b56ffcf9/raw/ad1d2e041edc0fb7ef23402e64eeb92c045b5ef7/bitcoin-file-downloader.py
pip install python-bitcoinrpc==1.0
BTCRPCURL=http://asdf:qwer@127.0.0.1:8332 \
  PYTHONPATH="$(pwd)/python-bitcoinrpc:$PYTHONPATH" \
  python3 bitcoin-file-downloader.py \
  54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713

где tx 54e48e5f5c656b26c3bca14a8c95aa583d07ebe84dde3b7dd4a78f4e4186e713 (блок 230009) — транзакция, в которой закодирован технический документ.

Кодировка, используемая скриптом загрузчика

Загрузчик не очень строг к тонкостям загрузчика. Мы можем сразу понять его формат из источника:

data = b''
for txout in tx['vout'][0:-2]:
    for op in txout['scriptPubKey']['asm'].split(' '):
        if not op.startswith('OP_') and len(op) >= 40:
            data += unhexlify(op.encode('utf8'))

length = struct.unpack('<L', data[0:4])[0]
checksum = struct.unpack('<L', data[4:8])[0]
data = data[8:8+length]

if checksum != crc32(data):
    print('Checksum mismatch; expected %d but calculated %d' % (checksum, crc32(data)),
          file=sys.stderr)
    sys.exit()

так что мы видим, что это:

  • игнорирует два последних выхода
  • игнорирует операнды
  • игнорирует любую целочисленную константу длиной менее 20 байт (40 шестнадцатеричных байтов)

Тогда из приведенных выше данных:

  • первые 4 байта - длина полезной нагрузки
  • следующие 4 байта — это crc32 остальных данных полезной нагрузки.
  • полезная нагрузка начинается с 8-го байта

Кодировка, используемая сценарием загрузчика

Загрузчик немного более конкретен. Пожалуй, проще всего это понять, посмотрев на разборку небольшой загрузки, например самого загрузчика: https://www.blockchain.com/btc/tx/6c53cd987119ef797d5adccd76241247988a0a5ef783572a9972e7371c5fb0cc

Сначала у нас есть куча выходов типа:

OP_1 data OP_3 OP_CHECKMULTISIG

и стоит крошечные 0,00000001 BTC, которые содержат большую часть данных. Предположительно, они не могут быть потрачены из-за OP_CHECKMULTISIG, но это также не может быть доказано.

Затем в конце у нас есть две разные транзакции, которые, как мы видели выше, загрузчик игнорирует:

  • стандартный P2PKH OP_DUP OP_HASH160 <hash> OP_EQUALVERIFY OP_CHECKSIGс минимальным значением 0,00000001 BTC и неизрасходованным. ТОДО почему? Это соответствует части кода:

    # dest output
    out_value = Decimal(sys.argv[3])
    change -= out_value
    txouts.append((out_value, OP_DUP + OP_HASH160 + pushdata(addr2bytes(sys.argv[2])) + OP_EQUALVERIFY + OP_CHECKSIG))
    

    который показывает, что вы просто передаете произвольное целевое значение и адрес из своего скрипта.

  • стандартная проводная транзакция изменения P2PKH со значительным значением, чтобы восстановить остатки от ввода. Он отправляется на новый адрес, принадлежащий вам, который создается «на лету» с помощью вызова RPC:

    change_addr = proxy.getnewaddress()
    txouts.append([change, OP_DUP + OP_HASH160 + pushdata(addr2bytes(change_addr)) + OP_EQUALVERIFY + OP_CHECKSIG])
    

Чтение источника в основном подтверждает то, что мы видим на разборке. Далее следуют некоторые другие указатели.

В:

data = open(sys.argv[1],'rb').read()
data = struct.pack('<L', len(data)) + struct.pack('<L', crc32(data)) + data
fd = io.BytesIO(data)

мы видим:

  • длина полезной нагрузкиstruct.pack('<L', len(data))
  • crc32

добавляется в начало файла.

В:

def checkmultisig_scriptPubKey_dump(fd):
    data = fd.read(65*3)
    if not data:
        return None

    r = pushint(1)

    n = 0
    while data:
        chunk = data[0:65]
        data = data[65:]

        if len(chunk) < 33:
            chunk += b'\x00'*(33-len(chunk))
        elif len(chunk) < 65:
            chunk += b'\x00'*(65-len(chunk))

        r += pushdata(chunk)
        n += 1

    r += pushint(n) + OP_CHECKMULTISIG
    return r

мы видим основной цикл кодирования данных. Это выясняется из разборки:

OP_1 data OP_3 OP_CHECKMULTISIG
  • фиксируется OP_1наpushint(1)

  • OP_3может быть меньше 3, он кодирует, сколько фрагментов по 65 байт было закодировано, при этом на каждом выходе присутствует максимум 3 фрагмента из- заfd.read(65*3)

    OP_1Таким образом , последняя транзакция полезной нагрузки может иметь OP_2или OP_3в зависимости от степени детализации.

Для чего еще использовался скрипт загрузчика и был ли он популярен?

В:

Я проиндексировал каждую транзакцию в блокчейне, которая следует указанному выше формату, в частности, имеющую соответствующий CRC32 в первых 8 байтах.

Мне также удалось загрузить и интерпретировать каждую из транзакций, как кратко описано по адресу: https://cirosantilli.com/cool-data-embedded-in-the-bitcoin-blockchain/illegal-content-of-block-229k .

По сути, очень скоро после того, как загрузчик/загрузчик были загружены на 229991, помимо технического документа, было сделано несколько загрузок, преднамеренно содержащих то, что некоторые страны могут считать незаконными данными, хотя ни одна из них не бросалась в глаза по моим сумасшедшим стандартам, и, как мы видели, до сих пор блокчейн Биткойн пережил их на данный момент по состоянию на 2021 год.

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

И после непродолжительного всплеска активности загрузка прекратилась, и намного позже была сделана только одна загрузка по адресу tx 89248ecadd51ada613cf8bdf46c174c57842e51de4f99f4bbd8b8b34d3cb7792, которая привлекла мое внимание, потому что она содержит изображение Будды в формате ASCII между двумя символами Инь-Ян, как описано по адресу: https:// cirosantilli.com/cool-data-embedded-in-the-bitcoin-blockchain/ascii-art

          69696969                         69696969
       6969    696969                   696969    6969
     969    69  6969696               6969  6969     696
    969        696969696             696969696969     696
   969        69696969696           6969696969696      696
   696      9696969696969           969696969696       969
    696     696969696969             969696969        969
     696     696  96969      _=_      9696969  69    696
       9696    969696      q(-_-)p      696969    6969
          96969696         '_) (_`         69696969
             96            /__/  \            69
             69          _(<_   / )_          96
            6969        (__\_\_|_/__)        9696

Поэтому мой вывод таков, что скрипты загрузчика и загрузчика просто никогда не пользовались значительной популярностью.

Они слишком перегружены и неуклюжи, чем нужно. Зачем вам нужен CRC32, когда сам блокчейн уже хеширован побайтно до краев? Возможно, он был задуман просто как маркер «интересной информации». Но гораздо более естественным и доминирующим позже было использование строк ASCII или просто магических байтов определенных форматов файлов в качестве маркеров, см., например, мои комментарии к более прямо закодированным изображениям по адресу: