Элегантное решение для управления изменениями

Мир меняется, а разработчики не идеальны. Первая итерация любого программного обеспечения всегда имеет недостатки или недоработки, которые необходимо исправить через некоторое время. По этим двум причинам важно иметь механизм управления изменениями. В случае 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):

  1. Получить шестнадцатеричную транзакцию.
  2. Поместите шестнадцатеричную транзакцию в мой инструмент декодирования.
  3. Найдите десятый индекс 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

Резюме

Биткойн-скрипт - это стековый язык программирования для блокировки и разблокировки транзакций. Это умный подход к управлению изменениями, поскольку он позволяет модифицировать основную функцию безопасности: «процесс утверждения» транзакций. Тот факт, что это очень простой стековый язык, предотвращает возникновение проблем с безопасностью в самом скрипте.

Ресурсы

Большое спасибо моему другу Рене Пикхардту за то, что помог мне разобраться в этой теме.