Как выкупить базовый Tx?

Учитывая необработанный стандартный Tx ( вики ):

01000000

01
26c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb
00000000
8b
48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078
ffffffff

02

b06c191e01000000
19
76a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac

00e1f50500000000
19
76a914010966776006953d5567439e5e39f86a0d273bee88ac
00000000

и закрытый ключ:

18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

Как построить новую транзакцию, чтобы выкупить монету из второго выхода? Я долго пытался понять это, используя диаграмму etotheipi , но, похоже, не могу понять, как должна быть создана правильная транзакция. Пошаговое руководство приветствуется.

in bitcoind, I used the resulting hex to do this signrawtransaction '0100000001eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2010000008a4730440220359f19e3d19dd707053641ff1efd0ca25159d0e0a8cbe13cbdcf38a9608c38ee02207383ebc663253a21101597ed897366e2a748ab29c18a8ca183d02910283e88cc01410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6ffffffff01605af405000000001976a914097072524438d003d23a2f23edb65aae1bb3e46988ac00000000' '[]' '[]' And it returned me 'false' ... I think there is a problem with the code ?
@ Даниэль, я не уверен, но это может быть потому, что биткойн-signrawtransaction требует реальной предыдущей выходной транзакции в блокчейне для расчета средств? хэш для транзакции, указанный в этом вопросе f2b3eb2deb76566e7324307cd47c35eeb88413f971d88519859b1834307ecfec, который, по словам blockexplorer.com, не существует
@mulllhausen Как это было сделано, если txid неправильный?

Ответы (3)

В этом ответе я рассмотрю шаги, необходимые для погашения второго выхода транзакции, перечисленного выше. Ответ будет ограничен выкупом вывода определенного типа, присутствующего в этой транзакции (вывод, который требует предоставления новой транзакции, подписанной закрытым ключом, чей хэш открытого ключа соответствует хешу в сценарии рассматриваемого вывода), как этот ответ уже довольно длинный, даже без учета других типов вывода.

Краткое резюме: мы начинаем с создания новой транзакции со scriptSig, содержащим scriptPubKey вывода, который мы хотим выкупить. scriptPubKey этой транзакции будет содержать скрипт, который платит по хэшу открытого ключа (биткойн-адреса). Мы выполняем двойной хэш SHA256 для этой транзакции с четырехбайтовым хэш-кодом типа SIGHASH_ALL, добавленным в конец. Мы подписываем этот хеш закрытым ключом, указанным выше. Затем scriptSig этой новой транзакции заменяется сценарием, который сначала помещает в стек подпись в кодировке DER, а также однобайтовый хэш-код типа SIGHASH_ALL, а затем соответствующий открытый ключ закрытого ключа в кодировке DER.

Пошаговое описание:

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

  1. Добавьте четырехбайтное поле версии:01000000
  2. Однобайтовый вариант, указывающий количество входов:01
  3. 32-байтный хэш транзакции, из которой мы хотим выкупить выход:eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
  4. Четырехбайтовое поле, обозначающее выходной индекс, который мы хотим выкупить из транзакции с указанным выше хэшем (выходной номер 2 = выходной индекс 1):01000000
  5. Теперь идет сценарий Sig. В целях подписания транзакции он временно заполняется scriptPubKey вывода, который мы хотим выкупить. Сначала мы пишем однобайтовый вариант, который обозначает длину scriptSig (0x19 = 25 байт):19
  6. Затем мы пишем временный scriptSig, который, опять же, является scriptPubKey вывода, который мы хотим выкупить:76a914010966776006953d5567439e5e39f86a0d273bee88ac
  7. Затем мы записываем четырехбайтовое поле, обозначающее последовательность. В настоящее время всегда установлено значение 0xffffffff:ffffffff
  8. Далее идет однобайтовый варинт, содержащий количество выходов в нашей новой транзакции. В этом примере мы установим это значение равным 1:01
  9. Затем мы записываем 8-байтовое поле (64-битное целое число), содержащее сумму, которую мы хотим получить от указанного выхода. Я установлю это на общую сумму, доступную на выходе, за вычетом комиссии в размере 0,001 BTC (0,999 BTC или 99900000 сатоши):605af40500000000
  10. Затем мы начинаем записывать вывод нашей транзакции. Начнем с однобайтового варинта, обозначающего длину выходного скрипта (0x19 или 25 байт):19
  11. Затем фактический сценарий вывода:76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
  12. Затем пишем четырехбайтное поле «время блокировки»:00000000
  13. И, наконец, пишем четырехбайтный «тип хеш-кода» (1 в нашем случае):01000000

    Теперь у нас есть следующие необработанные данные о транзакциях:

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    19
    76a914010966776006953d5567439e5e39f86a0d273bee88ac
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000
    01000000
    
  14. (этап подписания) Теперь мы дважды хешируем всю эту структуру SHA256, что дает хэш9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e

  15. Затем мы создаем пару открытый/закрытый ключ из предоставленного закрытого ключа. Мы подписываем хеш из шага 14 с помощью закрытого ключа, который дает следующую подпись в кодировке DER (эта подпись будет другой в вашем случае): 30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06К этой подписи мы добавляем однобайтный хеш-код типа: 01. Открытый ключ:0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  16. Мы создаем окончательный scriptSig путем объединения:

    • Однобайтовый OPCODE сценария, содержащий длину подписи в кодировке DER плюс 1 (длина типа однобайтового хэш-кода)
    • Фактическая подпись в кодировке DER плюс тип однобайтового хэш-кода
    • Однобайтовый скрипт OPCODE, содержащий длину открытого ключа
    • Фактический открытый ключ
  17. Затем мы заменяем однобайтовое поле длины varint из шага 5 на длину данных из шага 16. Длина составляет 140 байтов или 0x8C байтов:8c

  18. И мы заменяем временный scriptSig из шага 6 структурой данных, созданной на шаге 16. Это становится:4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
  19. Мы завершаем удалением четырехбайтового типа хэш-кода, который мы добавили на шаге 13, и получаем следующий поток байтов, который является финальной транзакцией:

    01000000
    01
    eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
    01000000
    8c
    4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
    ffffffff
    01
    605af40500000000
    19
    76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
    00000000
    

Пример кода Python:

Я создал пример скрипта Python, который делает все вышеперечисленное. Оно намеренно сделано как можно более подробным и подробно прокомментировано, с минимальным количеством функций, чтобы оно напоминало пошаговое руководство выше. Количество строк кода можно легко сократить до половины, но я предпочитаю публиковать его в таком подробном формате, поскольку считаю, что его проще всего следовать (т. е. без «прыжков» вперед и назад по функциям). Скрипт содержит 76 непустых строк без комментариев. Сценарий зависит от bitcointools (для сериализации и десериализации транзакций и кодирования/декодирования base58) и ecdsa_ssl.py.из моего форка репозитория joric's brutus (для создания пар открытых/закрытых ключей EC и подписи ECDSA). Самый простой способ запустить скрипт — клонировать bitcointools в папку и поместить ecdsa_ssl.py из указанного выше URL-адреса в ту же папку вместе с этим скриптом и запустить скрипт оттуда. Вы захотите заменить адрес в SEND_TO_ADDRESSпеременной в этом скрипте адресом, на который вы хотите отправить монеты, если вы не чувствуете себя щедрым :).

#биткоинтулс
from deserialize import parse_Transaction, коды операций
из BCDataStream импортировать BCDataStream
из base58 импортировать bc_address_to_hash_160, b58decode, public_key_to_bc_address, hash_160_to_bc_address

импортировать ecdsa_ssl

импортировать Crypto.Hash.SHA256 как sha256
импортировать Crypto.Random

#транзакция, с которой мы хотим выкупить выход
HEX_TRANSACTION="010000000126c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb000000008b48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078ffffffff02b06c191e010000001976a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac00e1f505000000001976a914010966776006953d5567439e5e39f86a0d273bee88ac00000000"
#Вывод для погашения. должен существовать в HEX_TRANSACTION
ВЫХОД_ИНДЕКС=1
#адрес, на который мы хотим отправить погашенные монеты.
#ЗАМЕНИТЕ СВОИМ АДРЕСОМ, если только вы не чувствуете себя щедрым
SEND_TO_ADDRESS="1L4xtXCdJNiYnyqE6UsB8KSJvqEuXjz6aK"
#комиссия, которую мы хотим заплатить (в BTC)
TX_FEE=0,001
#константа, определяющая количество сатоши на BTC
МОНЕТА=100000000
#constant используется для определения того, какая часть транзакции хешируется.
SIGHASH_ALL=1
#private key, чей открытый ключ хэшируется на хэш, содержащийся в scriptPubKey выходного номера *OUTPUT_INDEX* в транзакции, описанной в HEX_TRANSACTION
PRIVATE_KEY=0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725

защита dsha256 (данные):
   вернуть sha256.new (sha256.new (данные). дайджест ()). дайджест ()

tx_data=HEX_TRANSACTION.decode('hex_codec')
tx_hash = dsha256 (tx_data)

#здесь мы используем биткойнтулс для разбора транзакции. это дает легкий доступ к различным полям транзакции, из которых мы хотим выкупить вывод
поток = BCDataStream()
поток.запись (tx_data)
tx_info = parse_Transaction (поток)

если len(tx_info['txOut']) < (OUTPUT_INDEX+1):
   поднять RuntimeError, «в транзакции, которую вы пытаетесь использовать, есть только %d выходных данных. вы хотите использовать выходной индекс %d» % (len(tx_info['txOut']), OUTPUT_INDEX)

#этот словарь используется для хранения значений различных полей транзакций
# это полезно, потому что нам нужно построить одну транзакцию для хеширования и подписи
# и еще одна, которая будет последней транзакцией
tx_fields = {}

##здесь мы начинаем создавать транзакцию, которую мы хешируем и подписываем
sign_tx = BCDataStream()
##сначала пишем номер версии, который равен 1
tx_fields['версия'] = 1
sign_tx.write_int32 (tx_fields ['версия'])
##тогда мы записываем количество входов транзакции, то есть один
tx_fields['num_txin'] = 1
sign_tx.write_compact_size (tx_fields ['num_txin'])

##затем мы записываем фактические данные транзакции
#'prevout_hash'
tx_fields['prevout_hash'] = tx_hash
sign_tx.write(tx_fields['prevout_hash']) #хеш транзакции, из которой мы хотим выкупить вывод
#'prevout_n'
tx_fields['output_index'] = ВЫХОД_ИНДЕКС
sign_tx.write_uint32(tx_fields['output_index']) # какой вывод транзакции с идентификатором tx 'prevout_hash' мы хотим погасить?

##далее идет часть ввода транзакции. здесь мы размещаем скрипт *выхода*, который мы хотим выкупить
tx_fields['scriptSigHash'] = tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey']
#сначала напишите размер
sign_tx.write_compact_size (len (tx_fields ['scriptSigHash']))
#тогда данные
sign_tx.write(tx_fields['scriptSigHash'])

#'последовательность'
tx_fields['последовательность'] = 0xffffffff
sign_tx.write_uint32 (tx_fields ['последовательность'])

##затем пишем количество выходов транзакции. мы просто будем использовать один выход в этом примере
tx_fields['num_txout'] = 1
sign_tx.write_compact_size (tx_fields ['num_txout'])
##затем мы записываем фактические выходные данные транзакции
#мы выкупим все из исходного вывода за вычетом TX_FEE
tx_fields['value'] = tx_info['txOut'][OUTPUT_INDEX]['value']-(TX_FEE*COIN)
sign_tx.write_int64 (tx_fields ['значение'])
##здесь находится наш scriptPubKey (скрипт, который выплачивает по адресу)
# нам нужен следующий скрипт:
#"OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG"
address_hash = bc_address_to_hash_160(SEND_TO_ADDRESS)
#chr(20) — длина address_hash (20 байт или 160 бит).
scriptPubKey = chr(коды операций.OP_DUP) + chr(коды операций.OP_HASH160) + \
   chr(20) + address_hash + chr(коды операций.OP_EQUALVERIFY) + chr(коды операций.OP_CHECKSIG)
#сначала напишите длину этого куска данных
tx_fields['scriptPubKey'] = скриптPubKey
sign_tx.write_compact_size (len (tx_fields ['scriptPubKey']))
#тогда данные
sign_tx.write(tx_fields['scriptPubKey'])

#записать время блокировки (0)
tx_fields['время блокировки'] = 0
sign_tx.write_uint32 (tx_fields ['время блокировки'])
# и тип хэш-кода (1)
tx_fields['hash_type'] = SIGHASH_ALL
sign_tx.write_int32 (tx_fields ['hash_type'])

#затем получаем хэш транзакции без подписи (хэш, который мы подписываем с помощью нашего закрытого ключа)
hash_scriptless = dsha256 (sign_tx.input)

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

k = ecdsa_ssl.KEY()
k.generate(('%064x' % PRIVATE_KEY).decode('hex'))

#здесь мы извлекаем данные открытого ключа, сгенерированные из предоставленного закрытого ключа
pubkey_data = k.get_pubkey()
#затем мы создаем подпись поверх хэша транзакции без подписи
sig_data=k.sign(hash_scriptless)
# к концу подписи добавляется однобайтовый «хеш-тип» (https://en.bitcoin.it/wiki/OP_CHECKSIG)
sig_data = sig_data + chr(SIGHASH_ALL)

#давайте проверим, что предоставленный приватный ключ действительно может выкупить рассматриваемый вывод
if (bc_address_to_hash_160(public_key_to_bc_address(pubkey_data)) != tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2]):
   байты = b58decode(SEND_TO_ADDRESS, 25)
   поднять RuntimeError, "Предоставленный закрытый ключ не может быть использован для выкупа выходного индекса %d\nВам необходимо предоставить закрытый ключ для адреса %s" % \
                           (OUTPUT_INDEX, hash_160_to_bc_address(tx_info['txOut'][OUTPUT_INDEX]['scriptPubKey'][3:-2], bytes[0]))

##теперь мы начинаем создавать финальную транзакцию. это дубликат транзакции без подписи,
## со scriptSig, заполненным скриптом, который помещает подпись, однобайтовый хеш-код и открытый ключ сверху в стек

final_tx = BCDataStream()
final_tx.write_int32 (tx_fields ['версия'])
final_tx.write_compact_size (tx_fields ['num_txin'])
final_tx.write(tx_fields['prevout_hash'])
final_tx.write_uint32 (tx_fields ['output_index'])

##теперь нам нужно написать сам скриптSig.
## состоит из закодированных по DER значений r и s из подписи, однобайтового типа хэш-кода и открытого ключа в несжатом формате
## нам также нужно добавить длину этих двух фрагментов данных (закодированных как один байт
##, содержащий длину), перед каждой частью данных. эта длина является опкодом скрипта, который сообщает
## Интерпретатор биткойн-скриптов для помещения в стек следующих x байтов

scriptSig = chr (len (sig_data)) + sig_data + chr (len (pubkey_data)) + pubkey_data
#сначала напишите длину этих данных
final_tx.write_compact_size (длина (scriptSig))
#тогда данные
final_tx.write(scriptSig)

##а затем просто пишем те же данные после scriptSig, что и в безподписной транзакции,
# исключая тип четырехбайтового хеш-кода (поскольку он закодирован в одном байте, следующем за данными подписи)

final_tx.write_uint32 (tx_fields ['последовательность'])
final_tx.write_compact_size (tx_fields ['num_txout'])
final_tx.write_int64 (tx_fields ['значение'])
final_tx.write_compact_size (len (tx_fields ['scriptPubKey']))
final_tx.write(tx_fields['scriptPubKey'])
final_tx.write_uint32 (tx_fields ['время блокировки'])

#выводит окончательную транзакцию в шестнадцатеричном формате (может использоваться как аргумент для sendrawtransaction биткойна)
распечатать final_tx.input.encode('hex')
Вы уверены, что шаг 11 правильный? Он просто показывает исходный адрес. Разве это не должен быть сценарий типа «OP_DUP OP_HASH160 дестинативный_адрес_в_хэше_160_format OP_EQUALVERIFY OP_CHECKSIG»? Где OP_DUP заменяется на «76» и т. д., как описано здесь .
Я застрял на шаге 15... Есть идеи, что я могу делать неправильно? bitcoin.stackexchange.com/questions/10784/…
Что касается моего первого комментария, я только что заметил, что адрес действительно завернут в 76 a9 и 88 ac. Таким образом, он отправляет все обратно в источник.
Нет, не отправляет обратно, а отправляет на 1runeksijzfVxyrpiyCY2LCBvYsSiFsCm.
@SjorsProvoost Как уже упоминалось, сценарий выкупает выходной номер OUTPUT_INDEX из транзакции, описанной в шестнадцатеричном формате в HEX_TRANSACTION, и отправляет его на адрес, содержащийся в SEND_TO_ADDRESS.
Извините за недостаток знаний, но нормально ли, что скрипт дает разные результаты при каждом запуске? Я не знал о необходимости генерации случайных чисел при создании перевода (?)
@ Даниэль Да. Подписи ECDSA требуют случайного числа. Плохие вещи случаются, если они не случайны.
Если вы хотите погасить транзакцию с несколькими входами, как вы сгенерируете хэш, который вы должны подписать для каждого входа? Это один и тот же хеш для всех?
@runeks > «[...] замените адрес в переменной SEND_TO_ADDRESS [...], если вы не чувствуете себя щедрым :)» Просто для записей, кажется, что несколько душ почувствовали себя щедрыми после прочтения вашего ответа. Удивительна сила биткойна.
Что k.generate(('%064x' % PRIVATE_KEY).decode('hex'))делает? Должен ли я сгенерировать что-то из закрытого ключа и подписать с помощью ЭТОГО, или я просто подпишусь с помощью закрытого ключа?
Шаг «генерировать» создает объект закрытого ключа из шестнадцатеричных данных в PRIVATE_KEY. Итак, по сути, это просто преобразование 32 байтов данных в шестнадцатеричном формате в закрытый ключ.
Добавление к элегантному решению, данному runeks: в случае нескольких входов (что довольно часто) мы строим два разных tx, повторяя шаги 1-13. в первом tx мы сохраняем вход 2 как 00, а во втором tx мы сохраняем ввод 1 как 00. затем мы подписываем оба этих tx и заменяем их в двух входах, создавая 2 входа n выход со знаком tx.
пункт 3 находится в Little Endian
Биткойнтулс Гэвина не работает по крайней мере с ноября 2017 года. У Runeks есть непрямой форк на github.com/runeksvendsen/bitcointools , который, кажется, работает должным образом.
@blockwala что вы подразумеваете под «00» в данном вводе? просто заменить scriptsig или заменить структуру ввода отверстия (предыдущий хэш tx, предыдущий выходной индекс, длину скрипта, а также сигнал и последовательность скрипта)?

Этот gist , частично основанный на ответе Runeks, показывает, как перевести 0,01 биткойна в Ruby. Он извлекает информацию из предыдущей транзакции с Blockchain.info, поэтому вам просто нужно передать ему свой закрытый ключ и адрес (последний избыточен, но полезен для демонстрации). Я добавил много комментариев, чтобы объяснить необходимые шаги.

Как только вы узнаете идентификатор транзакции, vout, scriptPubKey этой транзакции и закрытый ключ в кодировке WIF, соответствующий открытому ключу, хешированному для получения адреса, вы можете создать необработанную транзакцию и подписать ее, не находясь в сети.

Я написал библиотеку PHP для работы с необработанными транзакциями (помимо других функций биткойнов). Вот как можно погасить обычную транзакцию: https://github.com/Bit-Wasp/bitcoin-lib-php Требуется обновление для поддержки подписей P2SH, но обычную транзакцию он погашает без проблем.