Криптовалюта широко известна прежде всего как инструмент для спекулятивной игры на биржах. В статье описываются внутреннее строение транзакций криптовалюты Bitcoin. В статье рассказывается о том, какое место в инфраструктуре сети Bitcoin отведено для языка программирования Script. Приведены примеры некоторых стандартных скриптов.
Введение
Bitcoin – это первая в мире криптовалюта основанная на технологии децентрализованного обмена транзакциями (1). Широкой публике Bitcoin известен прежде всего как инструмент для анонимных, не зависящих от банков платежей, а так же как инструмент для спекулятивной игры на биржах. Мало кто знает и задумывается над тем, что с технической точки зрения, все транзакции Bitcoin – это не что иное, как набор компьютерных программ. Сам код Bitcoin написан на языке программирования C++ (2), но для транзакций используется простой Тьюринг-неполный язык программирования который называется Script.
Утилиты bitcoin-qt, bitcoind, bitcoin-cli
В состав дистрибутива Bitcoin (3) входят несколько исполняемых файлов, из которых наиболее известным является графический клиент bitcoin-qt. Графический клиент позволяет в интерактивном режиме управлять многими функциями, однако зачастую самые новые функции разработчики в графический клиент не включают. Поэтому для изучения протокола Bitcoin на низком уровне, более полезной является утилита командной строки bitcoin-cli. Полный список команд этой утилиты можно получить стандартным ключем
Так же можно получить подробное описание каждой команды с примерами использования. Например
/bitcoin-cli help decoderawtransaction
В системах без графической оболочки вместо bitcoin-qt, для работы сети Bitcoin нужно запускать утилиту bitcoind. Сама по себе эта утилита не представляет никаких интерактивных возможностей, поэтому удобно запускать ее в качестве "демона" (фоновой программы) с соответствующим флагом.
Транзакции Bitcoin
Транзакции это одна из главных частей протокола Bitcoin. На низком уровне, транзакции представляют собой длинную строку из шестнадцатиричных чисел. С помощью утилиты "bitcoin-cli", транзакцию можно декодировать в формат JSON.
Существуют два вида транзакций:
1. Транзакции у которых есть входящие (открывающие) и исходящие (закрывающие) скрипты. Это самый распространенный вид транзакций, каждая из таких транзакций должна ссылаться на предшествующую.
2. Coinbase-транзакции. Эти транзакции не имеют входящих (открывающих) скриптов и не содержат в себе ссылку на предыдущую транзакцию. (
примечание: В шестнадцатиричном представалении в сoinbase-транзакциях все поля заполнены как и в обычных транзакциях, но поля отвечающие за входящие скрипты и ссылку на предыдущую транзакцию не несут в себе смысла)
Самая первая транзакция Bitcoin называется "genesis block coinbase" и это единственная транзакция которую нельзя декодировать утилитой bitcoin-cli (
примечание: на самом деле можно, но для этого нужно вручную подготовить входные данные, что находится за рамками данной статьи). Полную информацию о любой другой транзакции можно легко получить.
Рассмотрим вторую (следующую после genesis block coinbase) транзакцию в сети Bitcoin. Получить ее можно следующими командами:
# Получаем хэш первого блока транзакций
./bitcoin-cli getblockhash 1
# Полученный хэш первого блока подставляем в следующую команду
./bitcoin-cli getblock 00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048
На выходе получим JSON строку в которой содержится основная информация о самом первом (после genesis) блоке Bitcoin.
{
"result": {
"hash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048",
"confirmations": 547866,
"strippedsize": 215,
"size": 215,
"weight": 860,
"height": 1,
"version": 1,
"versionHex": "00000001",
"merkleroot": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
"tx": [
"0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"
],
"time": 1231469665,
"mediantime": 1231469665,
"nonce": 2573394689,
"bits": "1d00ffff",
"difficulty": 1,
"chainwork": "0000000000000000000000000000000000000000000000000000000200020002",
"previousblockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
"nextblockhash": "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd"
},
"error": null,
"id": null
}
В этом блоке есть всего одна транзакция, получим теперь информацию о ней
/bitcoin-cli decoderawtransaction 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 1
Эта команда вернет в формате JSON, информацию о самой первой (после genesis) транзакции в сети Bitcoin. Наиболее важными полями в полученном JSON объекте являются следующие:
"vin": [ {
"coinbase": "04ffff001d0104",
"sequence": 4294967295
} ],
"vout": [ {
"value": 50.00000000,
"n": 0,
"scriptPubKey": {
"asm": "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d 4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG",
"hex": "410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da758937951 5d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac",
"reqSigs": 1,
"type": "pubkey",
"addresses": [
"12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX"
] } } ],
Итак, во всех транзакциях начиная с самой первой, есть два поля "vin" и "vout". Первое поле у coinbase транзакций содержит служебную информацию (как в примере выше), в данной статье это поле нас не интересует. Второе поле у всех транзакций в обязательном порядке содержит микропрограмму на языке Script. Программу можно прочитать в формате "asm" или в формате "hex". Для декодирования программы из шестнадцатиричного формата можно так же использовать утилиту bitcoin-cli. Например выходной ("vout" или запирающий) скрипт из первой транзакции Bitcoin можно декодировать так:
./bitcoin-cli decodescript 410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac
На выходе получим:
{
"result": {
"asm": "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d 4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee OP_CHECKSIG",
"reqSigs": 1,
"type": "pubkey",
"addresses": [ "12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX" ],
"p2sh": "3BgShGBxRQrczVD6Ftj4z51FfgJ761FdSX"
},
"error": null,
"id": null
}
Таким образом еще раз можно убедиться, что в JSON описании транзакции, поля "hex" и "asm", содержат один и тот же скрипт.
Входные и выходные скрипты Bitcoin
Все микропрограммы-скрипты в транзакциях Bitcoin состоят из двух частей. Первая часть - выходной скрипт который записан в поле vout
.scriptPubKey. Вторая часть программы это входной скрипт, записанный в поле vin.scriptSig. Входной скрипт может отсутствовать у транзакций "coinbase", однако выходной скрипт должен присутствовать во всех без исключения транзакциях.
Существует множество правил по которым узлы сети Bitcoin проверяют правильность транзакции (4), но главное из правил можно сформулировать следующими словами: "входной скрипт проверяемой транзакции объединенный с выходным скриптом предыдущей транзакции, должен работать без ошибок и по окончании работы во внутреннем стеке должно быть значение, соответствующее логической истине". Продемонстрируем как работает данное правило на примере:
# Рассмотрим Bitcoin блок №170
./bitcoin-cli getblockhash 170
00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
./bitcoin-cli getblock 00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee
{
"result": {
"hash": "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee",
*******************
"tx": [
"b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082",
"f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"
],
*******************
}
Это первый блок Bitcoin в котором кроме транзакции "coinbase" есть еще одна транзакция и в этой транзакции присутствует входной скрипт. Рассмотрим данную транзакцию, в ней нас будет интересовать только часть "vin".
./bitcoin-cli getrawtransaction f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16 1
***********************
"vin": [ {
"txid": "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9",
"vout": 0,
"scriptSig": {
"asm": "304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181 522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09[ALL]",
"hex": "47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd4102201 81522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"
*******************
Рассмотрим процесс проверки данной транзакции узлом (клиентом) Bitcoin сети:
1. К клиенту сети Bitcoin приходит транзакция.
2. Клиент сети Bitcoin должен взять из этой транзакции значение полей vin
.txid, vin.vout, vin.scriptSig.
3. Значение vin.txid - должно совпадать со значением какой-либо ранее проверенной транзакции. В рассматриваемом примере 0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9 это хэш "coinbase" транзакции из блока № 9.
4. Значение vin.vout должно указывать на номер выходного скрипта в транзакции vin.txid. В рассматриваемом примере vin.vout = 0 значит для следующей проверки нужно взять нулевой выходной скрипт из транзакции 0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9. Рассмотрим эту транзакцию, в ней нас интересует поле "vout"
./bitcoin-cli getrawtransaction 0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9 1
*******************************
"vout": [ {
"value": 50.00000000,
"n": 0,
"scriptPubKey": {
"asm": "0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84c cf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3 OP_CHECKSIG",
"hex": "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb8 4ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac",
"reqSigs": 1,
"type": "pubkey",
"addresses": [
"12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S"
*********************************
5. Окончательный проверочный скрипт можно представить следующим псевдокодом
[проверяемая транзакция].vin[i].scriptSig + [ранее проверенная транзакция].vout[vin[i].vout].scriptPubKey
Или пошагово:
PUSH(0x47) 304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09
PUSH(0x41) 0411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3
OP_CHECKSIG
Первая команда помещает в стек цифровую подпись, вторая команда помещает в стек публичный ключ, последняя команда проверяет цифровую подпись на соответствие публичному ключу, если проверка проходит успешно, то в стек помещается логическая истина. В итоге правильную транзакцию может сделать только тот, у кого есть публичный и приватный криптографический ключ от цифровой подписи указанной в выходном (vout, запирающем) скрипте.
Язык программирования Script
В документации приведен список всего около ста команд (5), некоторые из которых зарезервированы и в настоящее время не должны использоваться в транзакциях. Скрипты в транзакциях могут быть "стандартными" и "нестандартными". Исходящие скрипты должны быть только стандартными. Нестандартные входные скрипты не делают транзакцию нестандартной, поэтому открыта возможность для написания пользовательских скриптов различной степени сложности.
В настоящее время к входному скрипту предъявляются всего три требования (6):
1. Скрипт не должен содержать синтаксических ошибок
2. Скрипт должен оставлять в стеке непустое значение.
3. В скрипте должно быть не более 15 операторов проверки цифровой подписи.
Язык программирования Script является Тьюринг-неполным, в нем нет операторов безусловного перехода. Поэтому невозможно, например, создание циклов. Однако благодаря наличию операций ветвления, все таки можно создавать достаточно сложные по логике нестандартные скрипты.
Заключение
Микропрограммы на языке Script являются неотъемлемой частью протокола Bitcoin. Программы состоят из двух частей: входного и выходного скрипта. Входной скрипт находится в проверяемой транзакции и выполняется в начале. Выходной скрипт находится в ранее проверенной транзакции и выполняется сразу после входного скрипта. Транзакция проходит проверку успешно, только если в конце работы всех частей скрипта, в стеке программы будет значение, соотвествующее логической истине.
Источники информации
1. Satoshi Nakamoto. Bitcoin: A Peer-to-Peer Electronic Cash System.
https://bitcoin.org/bitcoin.pdf2. Bitcoin Core integration/staging tree.
https://github.com/bitcoin/bitcoin3. Bitcoin Core.
https://github.com/bitcoin/bitcoin/releases4.
https://github.com/bitcoin/bitcoin/blob/29f429dc7d4c7e6cd012e749cadf89e3138bcab3/src/policy/policy.cpp#L805. Script.
https://en.bitcoin.it/wiki/Script6. Bitcoin Core.
https://github.com/bitcoin/bitcoin/blob/master/src/policy/policy.cpp#L174