Элегантное решение для управления изменениями
Мир меняется, а разработчики не идеальны. Первая итерация любого программного обеспечения всегда имеет недостатки или недоработки, которые необходимо исправить через некоторое время. По этим двум причинам важно иметь механизм управления изменениями. В случае API центральных служб у вас обычно есть версия API и график прекращения поддержки.
Но что вы делаете в распределенной системе? Как сохранить гибкость и безопасность системы?
У Биткойна есть элегантное решение этой проблемы: Биткойн-скрипт! Прочитав эту статью, вы поймете это. Пойдем!
Анатомия биткойн-транзакции
Новые биткойны добываются с каждым блоком, который добавляется в цепочку блоков примерно до 2140 года. Кто имеет доступ к какому биткойну, определяется путем отслеживания неизрасходованного вывода транзакции (UTXO):
Перейдя в Проводник блоков биткойнов и выбрав последний блок, мы видим 2738 транзакций в этом блоке. Взять одну транзакцию и показать необработанный шестнадцатеричный дамп:
010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5403df550a1b4d696e656420627920416e74506f6f6c373138fd004702c13ba070fabe6d6d6d9986fd3aedb9f8b00fab3c74c2e1d399fd0e6bc1d964aa57a08f78ac2d648002000000000000001eb50000c1377c00ffffffff04653c3a2a000000001976a91411dbe48cc6b617f9c6adaf4d9ed5f625b1c7cb5988ac0000000000000000266a24aa21a9ede0721e21cc17367eb4fcccf94c7e6ebc54757a693515f809307d4d6a31e0c8500000000000000000266a24b9e11b6d6eab9aaf2ccdc3cb05fb15989c512b84c8ef5f498559cd4d66e6104de8f9cdb100000000000000002b6a2952534b424c4f434b3a93320d7a461a95b953bd18e40aa031b5470d6258ccd82011999a5b290031529c0120000000000000000000000000000000000000000000000000000000000000000000000000
Структура этого формата следующая:
В этой статье интересны два поля: «сценарий разблокировки» и «сценарий блокировки». Когда добавляется новая транзакция, она должна предоставить сценарий разблокировки, который соответствует указанному UTXO (TX-ID и TX-Index). Сценарий блокировки вывода - это то, что вы ожидаете от других в будущем, чтобы использовать этот вывод. В самом прямом смысле вы определяете ключ.
Эти два поля скрипта содержат биткойн-скрипт. Он начинается с однобайтового кода операции (OP_CODE
). Это просто номер, связанный с операцией.
Декодирование с помощью Bitcoin Core
Возьмем эту транзакцию для следующего примера. Сначала я декодирую шестнадцатеричную транзакцию, чтобы:
{ "version_number": 1, "marker": null, "flag": null, "tx_ins": [ { "tx_id": "00f645c7443e367330410e526b152fc799c71dafd4971d8ed6ee37babbd581bb", "tx_index": 10, "script": "483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94", "sequence": 4294967295 } ], "tx_outs": [ { "satoshi": 16939, "script": "a914c29b367fe...bd1b29ef47a687" }, "... more outs..." ], "witness": { "stack_items": [ [] ] }, "locktime": 0 }
Интересно то, что двоичный скрипт разблокировки дается в шестнадцатеричной системе для первого (и единственного) ввода. Я выделил это жирным шрифтом в блоке выше.
Вы можете использовать следующий сценарий (в Ubuntu 20.04) для декодирования двоичного сценария биткойнов в ASM (язык ассемблера):
# Download and extract bitcoin-core $wget https://bitcoin.org/bin/bitcoin-core-0.20.0/bitcoin-0.20.0-x86_64-linux-gnu.tar.gz $
tar -xvf bitcoin-0.20.0-x86_64-linux-gnu.tar.gz$
cd bitcoin-0.20.0/bin/ # Run the daemon, but do not download blocks $ ./bitcoind -daemon -connect=0.0.0.0 # Finally! Decode a Bitcoin Script hex-encoded program $ ./bitcoin-cli decodescript "483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94" { "asm": "3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001 04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94", "type": "nonstandard", "p2sh": "3QEAyVJgyTXivsKQopPiGngaPwFWmzdHLL", "segwit": { "asm": "0 64c118d91f54d62936c29915c5cb61d39dc3353b005aca55528a714b607800de", "hex": "002064c118d91f54d62936c29915c5cb61d39dc3353b005aca55528a714b607800de", "reqSigs": 1, "type": "witness_v0_scripthash", "addresses": [ "bc1qvnq33kgl2ntzjdkzny2utjmp6wwuxdfmqpdv542j3fc5kcrcqr0qxaa2gw" ], "p2sh-segwit": "3EVPaAaNmZtuP4acv63USoRkSXPez4dX4z" } }
Это типичный сценарий разблокировки (выделен жирным шрифтом с клавишей “asm”
). Первая строка - это подпись сценария. Второй - открытый ключ.
Далее получаем скрипт блокировки из транзакции (индекс 10):
- Получить шестнадцатеричную транзакцию.
- Поместите шестнадцатеричную транзакцию в мой инструмент декодирования.
- Найдите десятый индекс
tx_outs
и найдитеscript
.
$ bitcoin-cli decodescript "76a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac" { "asm": "OP_DUP OP_HASH160 7ddb236e7877d5040e2a59e4be544c65934e573a OP_EQUALVERIFY OP_CHECKSIG", "reqSigs": 1, "type": "pubkeyhash", "addresses": [ "1CUTyyxgbKvtCdoYmceQJCZLXCde5akiX2" ], "p2sh": "3Jp8er9Srb9LMkJqk8jPeWafjDztYGLyLn", "segwit": { "asm": "0 7ddb236e7877d5040e2a59e4be544c65934e573a", "hex": "00147ddb236e7877d5040e2a59e4be544c65934e573a", "reqSigs": 1, "type": "witness_v0_keyhash", "addresses": [ "bc1q0hdjxmncwl2sgr32t8jtu4zvvkf5u4e64ucrwj" ], "p2sh-segwit": "37e78P9jyHEZ7dp2as7w817BUPgEEUQDD2" } }
Значение ключа asm
- очень типичный сценарий блокировки.
Разблокировка транзакции
Чтобы выполнить оба, мы сначала помещаем скрипт разблокировки, а затем скрипт блокировки:
3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001
04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94
OP_DUP
OP_HASH160
7ddb236e7877d5040e2a59e4be544c65934e573a
OP_EQUALVERIFY
OP_CHECKSIG
Это выполняется, как и всякая программа, сверху вниз. Биткойн-скрипт - это язык программирования на основе стека. Это означает, что у вас нет переменных, а все складывается в стек.
Вы определенно хотите это выполнить. Есть онлайн-инструмент, но вроде есть баги. Вместо этого я рекомендую использовать btcdeb. После его установки можно запустить:
$ btcc 3045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d029001 04c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e94 OP_DUP OP_HASH160 7ddb236e7877d5040e2a59e4be544c65934e573a OP_EQUALVERIFY OP_CHECKSIG 483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e9476a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac $ btcdeb 483045022100b7393ff959120e3ccb5284e3cf2eaa200235643a1549a4e6faaa911619089e2b02207b677827c7beeb53503e016a8dd29164d07cb79f0f1e058df9b8dfa3568d0290014104c4b7a7f7bb2c899f4aeab75b41567c040ae79506d43ee72f650c95b6319e47402f0ba88d1c5a294d075885442679dc24882ea37c31e0dbc82cfd51ed185d7e9476a9147ddb236e7877d5040e2a59e4be544c65934e573a88ac btcdeb 0.4.21 -- type `btcdeb -h` for start up options LOG: sign segwit taproot notice: btcdeb has gotten quieter; use --verbose if necessary (this message is temporary) 7 op script loaded. type `help` for usage information script | stack ---------------------+-------- 3045022100b7393ff... | 04c4b7a7f7bb2c899... | OP_DUP | OP_HASH160 | 7ddb236e7877d5040... | OP_EQUALVERIFY | OP_CHECKSIG | btcdeb> step
Строка 3045022...
означает, что вы помещаете это значение в стек. Сверху ставится строка 04c4b...
. Затем выполняется OP_DUP
. Требуется один ввод: 04c4b...
. Этот дублируется. Таким образом, 04c4b...
помещается в стек дважды. Теперь у нас есть:
script | stack -------------------+------------- OP_HASH160 | 04c4b... 7ddb23... | 04c4b... OP_EQUALVERIFY | 3045022... OP_CHECKSIG |
OP_HASH160
принимает один аргумент (04c4b...
) и применяет к нему хеш-функцию. Тогда у вас есть:
script | stack -------------------+------------ 7ddb23... | 7ddb23... OP_EQUALVERIFY | 04c4b... OP_CHECKSIG | 3045022...
Далее мы кладем в стек дополнительно 7ddb23...
. OP_EQUALVERIFY
берет два 7ddb23...
в стек. Поскольку они одинаковы, он оценивается как True
и просто продолжает выполнение. В противном случае он бы прервался и вернул False
. Наконец, OP_CHECKSIG
сравнивает два оставшихся значения: открытый ключ и подпись сценария. Если они совпадают, то транзакция разблокирована!
Скрытые биткойн-сообщения
Поскольку вы можете поместить немного данных в биткойн-скрипт, вы можете помещать сообщения в цепочку блоков. Вам просто нужно убедиться, что сценарий по-прежнему оценивается как True
. Интересна первая биткойн-транзакция. Он содержит сценарий ввода:
04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73
Теперь нам нужно сделать две незначительные модификации:
$ ./bitcoin-cli decodescript "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73" { "asm": "486604799 4 5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", "type": "nonstandard", "p2sh": "3FimpcNzLCfeJB3zxDAExME4w6BLYryx4z", "segwit": { "asm": "0 c02688beb2fb8f93780d095ec5ce1a0213fafe72422744f618ab9286e3026020", "hex": "0020c02688beb2fb8f93780d095ec5ce1a0213fafe72422744f618ab9286e3026020", "reqSigs": 1, "type": "witness_v0_scripthash", "addresses": [ "bc1qcqng304jlw8ex7qdp90vtns6qgfl4lnjggn5fasc4wfgdcczvqsqzq3rpk" ], "p2sh-segwit": "3Nh7AqMGnHaY2x5Lts4BhV3hrU8ANceMmT" } } $ python >>> from binascii import unhexlify >>> unhexlify("5468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73") b'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'
Кстати, есть и другие способы скрыть сообщения (например, посмотреть на эти открытые ключи (WeRe fine, 8chaN poSt Fake). Если вы посмотрите на скрипт этой транзакции, вы найдете в выводе с индексом 2: Не будь побежден злом, но побеждай зло добром - Римлянам 12:21
Резюме
Биткойн-скрипт - это стековый язык программирования для блокировки и разблокировки транзакций. Это умный подход к управлению изменениями, поскольку он позволяет модифицировать основную функцию безопасности: «процесс утверждения» транзакций. Тот факт, что это очень простой стековый язык, предотвращает возникновение проблем с безопасностью в самом скрипте.
Ресурсы
Большое спасибо моему другу Рене Пикхардту за то, что помог мне разобраться в этой теме.
- Рене Пикхардт: Разделение транзакции P2PKH Биткойн до последнего байта »на YouTube, 2018.
- Скрипт на bitcoin.it: Объясняются различные коды OP.