Как выкупить базовый Tx?
Учитывая исходный, стандартный Tx ( wiki ):
01000000
01
26c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb
00000000
8b
48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078
ffffffff
02
b06c191e01000000
19
76a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac
00e1f50500000000
19
76a914010966776006953d5567439e5e39f86a0d273bee88ac
00000000
и закрытый ключ:
18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725
Как построить новую транзакцию, чтобы выкупить монету со второго выхода? Я пытался понять это долгое время, используя диаграмму etotheipi , но не может понять, как должна быть создана правильная транзакция. Пошаговое руководство будет приветствоваться.
3 ответа
В этом ответе я рассмотрю шаги, необходимые для выкупа второго выхода транзакции, указанной выше. Ответ будет ограничен выкупом вывода конкретного типа, присутствующего в этой транзакции (вывод, который требует предоставления новой транзакции, подписанной с закрытым ключом, чьи соответствующие общедоступные ключи хэшируют в хэш в скрипте соответствующего выпуска), так как этот ответ уже довольно длинный, даже без учета других типов выходных данных.
Краткое описание: Начнем с создания новой транзакции с помощью скриптаSig, содержащего scriptPubKey для вывода, который мы хотим выкупить. ScriptPubKey этой транзакции будет содержать скрипт, который платит за хэш открытого ключа (адрес биткойна). Мы выполняем двойной SHA256 хэш в этой транзакции с четырехбайтовым хэш-кодом типа SIGHASH_ALL, добавленным до конца. Мы подписываем этот хэш с закрытым ключом, указанным выше. Затем скриптSig этой новой транзакции заменяется сценарием, который сначала подталкивает сигнатуру DER-кодировки, а также однобайтовый код хэш-кода SIGHASH_ALL, в стек, за которым следует соответствующий открытый ключ DER-закодированного закрытого ключа.
Пошаговое описание:
Мы начинаем создавать новую сырую транзакцию, которую мы хэш и подписываем.
- Добавить четырехбайтное поле версии:
01000000
- Однобайтный varint, определяющий количество входов:
01
- 32-байтовый хэш транзакции, из которой мы хотим выкупить вывод:
eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2
- Четырехбайтовое поле, обозначающее выходной индекс, который мы хотим выкупить из транзакции с вышеуказанным хешем (номер выхода 2 = индекс вывода 1):
01000000
- Теперь идет скриптSig. Для подписания транзакции это временно заполняется скриптом scriptPubKey для вывода, который мы хотим выкупить. Сначала мы пишем однобайтный varint, который обозначает длину скриптаSig (0x19 = 25 байт):
19
- Затем мы записываем временный скриптSig, который, опять же, является scriptPubKey для вывода, который мы хотим выкупить:
76a914010966776006953d5567439e5e39f86a0d273bee88ac
- Затем мы записываем четырехбайтовое поле, обозначающее последовательность. В настоящее время всегда установлено значение 0xffffffff:
ffffffff
- Далее идет однобайтная varint, содержащая количество выходов в нашей новой транзакции. В этом примере мы установим значение 1:
01
- Затем мы записываем 8-байтовое поле (целое число 64 бит), содержащее сумму, которую мы хотим выкупить из указанного вывода. Я поставлю это на общую сумму, доступную на выходе минус 0,001 BTC (0,999 BTC, или 99900000 Satoshis):
605af40500000000
- Затем мы начинаем писать вывод нашей транзакции. Мы начинаем с однобайтовой переменной, обозначающей длину выходного скрипта (0x19 или 25 байт):
19
- Тогда фактический выходной скрипт:
76a914097072524438d003d23a2f23edb65aae1bb3e46988ac
- Затем мы записываем четырехбайтовое поле «время блокировки»:
00000000
-
И наконец, мы пишем четырехбайтовый «хэш-код типа» (1 в нашем случае):
01000000
Теперь у нас есть следующие необработанные данные транзакции:
01000000 01 eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2 01000000 19 76a914010966776006953d5567439e5e39f86a0d273bee88ac ffffffff 01 605af40500000000 19 76a914097072524438d003d23a2f23edb65aae1bb3e46988ac 00000000 01000000
-
(этап подписания) Теперь мы удваиваем SHA256 хэш всей этой структуры, которая дает хеш
9302bda273a887cb40c13e02a50b4071a31fd3aae3ae04021b0b843dd61ad18e
- Затем мы создаем пару открытого /закрытого ключа из предоставленного закрытого ключа. Мы подписываем хэш от шага 14 с помощью закрытого ключа, который дает следующую DER-закодированные подписи (эта подпись будет отличаться в вашем случае):
30460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc06
К этой подписи мы добавляем один-байтовый хэш-код тип:01
. Открытый ключ:0450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
- Мы строим окончательный скриптSig путем конкатенации: <Однобайтовый сценарий OPCODE, содержащий длину DER-кодированной сигнатуры плюс 1 (длина однобайтового типа хэш-кода)> | <Фактическая DER-кодированная подпись плюс однобайтовый тип хэш-кода> | <Однобайтовый сценарий OPCODE, содержащий длину открытого ключа> | <Фактический открытый ключ>
- Затем мы заменяем однобайтное поле длины varint с шага 5 длиной данных с шага 16. Длина составляет 140 байтов или 0x8C байтов:
8c
- И мы заменяем временный скриптSig с шага 6 структурой данныхпостроенный на шаге 16. Это будет выглядеть так:
4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6
-
Мы закончили с удалением четырехбайтового хеш-кода, который мы добавили на шаге 13, и в итоге получим следующий поток байтов, который является окончательной транзакцией:
01000000 01 eccf7e3034189b851985d871f91384b8ee357cd47c3024736e5676eb2debb3f2 01000000 8c 4930460221009e0339f72c793a89e664a8a932df073962a3f84eda0bd9e02084a6a9567f75aa022100bd9cbaca2e5ec195751efdfac164b76250b1e21302e51ca86dd7ebd7020cdc0601410450863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b23522cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6 ffffffff 01 605af40500000000 19 76a914097072524438d003d23a2f23edb65aae1bb3e46988ac 00000000
Пример кода Python:
Я создал пример скрипта Python, который делает все вышеперечисленное. Он преднамеренно максимально подробен и очень прокомментирован с минимальными функциями, чтобы походить на пошаговое руководство выше. Количество строк кода можно легко свести к одной половине, но я решил опубликовать его в этом подробном формате, поскольку я считаю, что это проще всего (т. Е. «Прыгать» назад и вперед через функции). Сценарий содержит 76 непустых строк без комментария.
Скрипт зависит от bitcointools от Gavin Andresen (для сериализации и десериализации транзакций и кодирования /декодирования base58) и ecdsa_ssl.py из моей вилки из ритуального хранилища joric (для создания публичного /частного EC ключевые пары и подписание ECDSA). Самый простой способ запустить скрипт - клонировать bitcointools в папку и поместить ecdsa_ssl.py из вышеуказанного URL-адреса в той же папке вместе с этим скриптом и выполнить скрипт оттуда.
Вы захотите заменить адрес в переменной SEND_TO_ADDRESS
в этом скрипте с адресом, на который вы хотите отправить монеты, если только вы не чувствуете себя щедрым:).
#bitcointools из deserialize import parse_Transaction, opcodes из BCDataStream импортировать BCDataStream из базы58 import bc_address_to_hash_160, b58decode, public_key_to_bc_address, hash_160_to_bc_address import ecdsa_ssl импортировать Crypto.Hash.SHA256 как sha256 Импорт Crypto.Random #transaction, из которого мы хотим выкупить вывод HEX_TRANSACTION = "010000000126c07ece0bce7cda0ccd14d99e205f118cde27e83dd75da7b141fe487b5528fb000000008b48304502202b7e37831273d74c8b5b1956c23e79acd660635a8d1063d413c50b218eb6bc8a022100a10a3a7b5aaa0f07827207daf81f718f51eeac96695cf1ef9f2020f21a0de02f01410452684bce6797a0a50d028e9632be0c2a7e5031b710972c2a3285520fb29fcd4ecfb5fc2bf86a1e7578e4f8a305eeb341d1c6fc0173e5837e2d3c7b178aade078ffffffff02b06c191e010000001976a9143564a74f9ddb4372301c49154605573d7d1a88fe88ac00e1f505000000001976a914010966776006953d5567439e5e39f86a0d273bee88ac00000000" #output для выкупа. должен существовать в HEX_TRANSACTION OUTPUT_INDEX = 1 #address мы хотим отправить выкупленные монеты. # РАЗМЕЩАЙТЕ СВОИМ СОБСТВЕННЫМ АДРЕСОМ, если вы не чувствуете щедрую SEND_TO_ADDRESS = "1L4xtXCdJNiYnyqE6UsB8KSJvqEuXjz6aK" #fee мы хотим заплатить (в BTC) TX_FEE = 0,001 #constant, который определяет количество Satoshis за BTC МОНЕТЫ = 100000000 #constant используется для определения, какая часть транзакции хеширована. SIGHASH_ALL = 1 #private key, чьи хэши-ключи общего доступа к хешу, содержащиеся в файле scriptPubKey выходного номера * OUTPUT_INDEX * в транзакции, описанной в HEX_TRANSACTION Private_key = 0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 def dsha256 (данные): return sha256.new (sha256.new (data) .digest ()). digest () tx_data = HEX_TRANSACTION.decode ( 'hex_codec') tx_hash = dsha256 (tx_data) #here мы используем bitcointools для анализа транзакции. это дает легкий доступ к различным областям транзакции, из которых мы хотим выкупить выход stream = BCDataStream () stream.write (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 ['version'] = 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']) #hash транзакции, из которой мы хотим выкупить вывод # 'prevout_n' tx_fields ['output_index'] = OUTPUT_INDEX sign_tx.write_uint32 (tx_fields ['output_index']) # какой вывод транзакции с tx id 'prevout_hash' мы хотим выкупить? ## next - это часть ввода транзакции. здесь мы помещаем скрипт * output *, который мы хотим выкупить tx_fields ['scriptSigHash'] = tx_info ['txOut'] [OUTPUT_INDEX] ['scriptPubKey'] # первый напишите размер sign_tx.write_compact_size (LEN (tx_fields [ 'scriptSigHash'])) # then данные sign_tx.write (tx_fields [ 'scriptSigHash']) #'последовательность' tx_fields ['sequence'] = 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] ['значение'] - (TX_FEE * COIN) sign_tx.write_int64 (tx_fields [ 'значение']) ## вот где наш скриптPubKey идет (скрипт, который платит за адрес) # Мне нужен следующий скрипт: # "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 (opcodes.OP_DUP) + chr (opcodes.OP_HASH160) + \ chr (20) + address_hash + chr (opcodes.OP_EQUALVERIFY) + chr (opcodes.OP_CHECKSIG) # сначала напишите длину этого куска данных tx_fields ['scriptPubKey'] = scriptPubKey sign_tx.write_compact_size (LEN (tx_fields [ 'scriptPubKey'])) # then данные sign_tx.write (tx_fields [ 'scriptPubKey']) #write locktime (0) tx_fields ['locktime'] = 0 sign_tx.write_uint32 (tx_fields [ 'Locktime']) # и тип хеш-кода (1) tx_fields ['hash_type'] = SIGHASH_ALL sign_tx.write_int32 (tx_fields [ 'hash_type']) # then мы получаем хеш транзакции без подписи (хеш, который мы подписываем, используя наш закрытый ключ) 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 () # then мы создаем подпись над хэшем транзакции без подписи sig_data = k.sign (hash_scriptless) #a один байтовый «хеш-тип» добавляется к концу подписи (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]): bytes = b58decode (SEND_TO_ADDRESS, 25) raise RuntimeError: «Предоставленный закрытый ключ не может использоваться для выкупа индекса вывода% d \ nВы необходимо предоставить закрытый ключ для адреса% s% (OUTPUT_INDEX, hash_160_to_bc_address (tx_info ['txOut'] [OUTPUT_INDEX] ['scriptPubKey'] [3: -2], байты [0])) ## теперь мы начинаем создавать окончательную транзакцию. это дубликат транзакции без подписи, ## с скриптом, заполненным скриптом, который подталкивает подпись и однобайтовый тип хеш-кода и открытый ключ сверху, в стек 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 (LEN (scriptSig)) # then данные final_tx.write (scriptSig) ##, а затем мы просто записываем одни и те же данные после скриптаSig, который находится в транзакции без подписи, # оставляя четырехбайтовый хэш-код (поскольку он закодирован в одном байте после подписиданные) 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 [ 'Locktime']) # отпечатывает окончательную транзакцию в шестнадцатеричном формате (может использоваться как аргумент для sendrawtransaction bitcoind) print final_tx.input.encode ('hex')
Этот смысл , частично основанный на ответе Рунекса, показывает, как перенести 0.01 биткойнов в Рубин. Он извлекает информацию из предыдущей транзакции из Blockchain.info, поэтому вам просто нужно передать ей свой секретный ключ и адрес (последний является избыточным, но полезным для демонстрации). Я добавил много комментариев, чтобы объяснить предпринятые шаги.
После того, как вы знаете идентификатор транзакции, vout, scriptPubKey этой транзакции и закрытый ключ WIF-кода, соответствующий открытому ключу, чтобы получить адрес, вы можете создать необработанную транзакцию и подписать ее, не будучи в сети .
Я написал библиотеку PHP для обработки необработанных транзакций (среди других функций биткойнов). Вот как вы могли бы выкупить обычную транзакцию: https://github.com/Bit-Wasp/bitcoin-lib-php Он нуждается в обновлении для поддержки подписей P2SH, но он будет эффективно использовать обычную транзакцию.