Введение в Bitcoin Script
Главная
Статьи
Введение в Bitcoin Script

Введение в Bitcoin Script

Профессионал
Опубликовано Jul 10, 2020Обновлено Jan 18, 2022
13m

Введение

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

Если владелец ключа хочет передать кому-то деньги, он открывает сейф. Для этого создается новый чек со ссылкой на старый (который уничтожается) и запирается в сейфе, который сможет открыть получатель. Чтобы потратить средства, новый получатель повторяет этот процесс.

В этой статье мы подробно рассмотрим Bitcoin Script – язык программирования, используемый нодами в сети Биткоин. Скрипт управляет механизмом блокировки/разблокировки сейфов.


Как работает биткоин?

Согласно приведенной выше аналогии, каждая транзакция состоит из двух частей – ключа (для разблокировки сейфа) и замка. Ключ используется для открытия сейфа с чеком, который вы хотите отправить, после чего добавляется новый чек в новый сейф с другим замком. Чтобы потратить средства из нового сейфа, понадобится еще один ключ.
Это совсем не сложно. Вы можете работать с различными типами замков в системе. Для некоторых сейфов может потребоваться несколько ключей, а для некоторых потребуется доказать право на открытие. Пользователи могут самостоятельно настраивать множество условий. 
Ключ здесь называется scriptSig, а замок – scriptPubKey. При ближайшем рассмотрении мы увидим, что эти компоненты состоят из битов данных и блоков кода, а в сочетании друг с другом создают крошечную программу.

Во время совершения транзакции эта комбинация транслируется в сеть. Каждая нода, получившая ее, проверяет действительность транзакции. Если сделать это не удается, вы не можете пользоваться заблокированными средствами.

Чеки (монеты) называются неизрасходованными выходами транзакций (UTXO). Деньги может использовать любой пользователь с подходящим ключом. В частности, ключ – это scriptSig, а замок – scriptPubKey.
Если в вашем кошельке есть UTXO, они, скорее всего, будут иметь условие, что только человек, доказавший владение публичным ключом, может разблокировать средства. Для их разблокировки необходимо предоставить scriptSig с цифровой подписью с помощью приватного ключа, который сопоставляется с публичным ключом, указанным в scriptPubKey. Далее мы рассмотрим это подробнее.


Основы стека Биткоина

Скрипт – это язык на основе стека. То есть когда мы читаем набор инструкций, они помещаются в некий вертикальный столбец. Например, список A, B, C образуют стек с A внизу и C вверху. Выполняя инструкции, мы начинаем работу с элементами с вершины стека.


Элементы A, B и C добавляются и «выталкиваются» из стека.


Данные (такие как подписи, хеши и публичные ключи) отличаются от инструкций (или опкодов). Инструкции убирают данные и выполняют с ними операции. Рассмотрим простой пример того, как может выглядеть скрипт:
<xyz> <md5 hasher> <d16fb36f0911f878998c136191af705e> <check if equal>
Красным цветом обозначены данные, синим – опкоды. Мы читаем слева направо, поэтому сначала помещаем в стек строку <xyz>. Далее следует опкод <md5 hasher>. Этого нет в Биткоине, но предположим, что он удаляет верхний элемент стека (<xyz>) и хеширует его с помощью алгоритма MD5. Затем результат снова добавляется в стек. Результат здесь будет d16fb36f0911f878998c136191af705e.
Какое совпадение! Следующий добавляемый элемент – это <d16fb36f0911f878998c136191af705e>, поэтому теперь в нашем стеке есть два идентичных элемента. Наконец, <check if equal> выдвигает два элемента сверху и проверяет, равны ли они. Если да, то в стек добавляется <1>, а в противном случае – <0>
Мы подошли к концу списка инструкций. Скрипт мог потерпеть неудачу при двух условиях: если оставшийся элемент равен нулю или один из операторов вызывает сбой при невыполнении некоторых условий. В этом примере у нас не было таких операторов, и оставшийся элемент не был равен нулю (<1>), поэтому наш скрипт был действителен. Эти правила действуют и для реальных биткоин-транзакций.

Но это была просто условная программа. Давайте рассмотрим реальные примеры.


Pay-to-Pubkey (P2PK)

Pay-to-Pubkey (P2PK) невероятно прост. Он включает в себя блокировку средств определенным публичным ключом. Если вы хотите получать средства таким образом, то должны предоставить отправителю свой публичный ключ, а не биткоин-адрес. 

Самой первой транзакцией между Сатоши Накамото и Хэлом Финни в 2009 году была транзакция P2PK. Эта структура активно использовалась на заре Биткоина, но в настоящее время ее в значительной степени заменил Pay-to-Pubkey-Hash (P2PKH). 
Скрипт блокировки для транзакции P2PK следует формату <public key> OP_CHECKSIG. Все просто. Вы могли догадаться, что OP_CHECKSIG проверяет подпись по предоставленному публичному ключу. Таким образом, scriptSig будет записан как <signature>. Помните, что scriptSig – это ключ к замку.



Проще не бывает. В стек добавляется подпись, за которой следует публичный ключ. OP_CHECKSIG извлекает их обоих и проверяет подпись публичного ключа. Если они совпадают, то в стек добавляется <1>, а если нет – <0>.

P2PK больше не используется, а причину этого мы рассмотрим в следующем разделе.


Pay-to-Pubkey-Hash (P2PKH)

Pay-to-Pubkey-Hash (P2PKH) является наиболее распространенным типом транзакций. Если только вы не используете устаревшее ПО, ваш кошелек, скорее всего, делает это по умолчанию.

scriptPubKey в P2PKH:

OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG

Прежде чем мы представим scriptSig, давайте разберемся, что будут делать новые опкоды:


OP_DUP

OP_DUP выталкивает первый элемент, дублирует его и добавляет обе версии в стек. Это делается, чтобы выполнять операции на дубликате, не влияя на оригинал.


OP_HASH160

Выталкивает первый элемент и дважды хеширует его. Первый раунд будет хешироваться с помощью алгоритма SHA-256, а выходной сигнал SHA-256 – с помощью алгоритма RIPEMD-160. Затем результат снова будет добавлен в стек.


OP_EQUALVERIFY

OP_EQUALVERIFY объединяет операторов OP_EQUAL и OP_VERIFY. OP_EQUAL выдвигает два элемента и проверяет, идентичны ли они. Если да, то в стек добавляется <1>, а если нет – он добавляет 0. OP_VERIFY выталкивает верхний элемент и проверяет его подлинность (т. е. не равно нулю). В противном случае транзакция не выполняется. В сочетании с OP_EQUALVERIFY это приводит к сбою транзакции, если два верхних элемента не совпадают.

На этот раз scriptSig выглядит следующим образом:

<signature> <public key>

Вам необходимо предоставить подпись и соответствующий публичный ключ для разблокировки выходов P2PKH.



Посмотрите на GIF-изображение выше. Оно практически полностью отображает скрипт P2PK. Мы просто добавляем дополнительный шаг для проверки соответствия публичного ключа хешу в скрипте.

Однако здесь есть, на что обратить внимание. В скрипте блокировки P2PKH публичный ключ не виден, мы можем видеть только его хеш. Если мы перейдем в обозреватель блокчейна и посмотрим на вывод P2PKH, который не был потрачен, мы не сможем узнать публичный ключ. Он раскрывается, когда получатель решает перевести средства.
У такой системы есть несколько преимуществ. Во-первых, хеш публичного ключа проще передать, чем полный публичный ключ. Именно по этой причине Сатоши запустил его в 2009 году. Хеш публичного ключа – это и есть биткоин-адрес.
Второе преимущество заключается в том, что хеши с открытым ключом могут обеспечить дополнительный уровень защиты от квантовых вычислений. Поскольку наш публичный ключ скрыт, пока мы не начинаем тратить средства, посторонним лицам будет еще труднее вычислить приватный ключ. Для его получения было бы необходимо реверсировать два цикла хеширования (RIPEMD-160 и SHA-256).



Pay-to-Script-Hash (P2SH)

Pay-to-Script-Hash (P2SH) стал очень интересной разработкой для Биткоина. Он позволяет отправителю заблокировать средства в хеше скрипта без необходимости понимать, что именно делает скрипт. Ознакомьтесь с примером хеша SHA-256:

e145fe9ed5c23aa71fdb443de00c7d9b4a69f8a27a2e4fbb1fe1d0dbfb6583f1

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

Приведенный выше хеш был создан из следующего скрипта:

<multiply by 2> <4> <check if equal>

Если вы хотите потратить монеты, привязанные к scriptPubKey, вам понадобится не только предоставить эти команды, но также воспользоваться scriptSig, который заставляет завершенный скрипт вынести статус True (Верно). В данном примере этот элемент умножается на 2 – <multiply by 2> – и мы получаем результат <4>. Это означает, что наш scriptSig – это <2>.

scriptPubKey для выхода P2SH:

OP_HASH160 <redeemScript hash> OP_EQUAL

Здесь нет новых операторов, но есть новый элемент <redeemScript hash>. Как следует из названия, это хеш скрипта, который нужно предоставить для возврата средств (redeemScript). ScriptSig будет меняться в зависимости от того, что находится в redeemScript. Это комбинация подписей и прикрепленных публичных ключей, за которыми следует (обязательный) redeemScript:

<signature> <public key> <redeemScript>

Наша оценка немного отличается от выполнения стека, которое мы наблюдали ранее. Это происходит в два этапа, первый из которых проверяет правильность хеша. 



Вы заметите, что мы ничего не делаем с элементами, предшествующими redeemScript, так как на данный момент они не используются. Мы достигли конца этой мини-программы, и верхний элемент не равен нулю, а значит, программа действительна.

Однако это еще не все. Сетевые ноды распознают эту структуру как P2SH, поэтому у них есть элементы scriptSig в ожидании в другом стеке. Там будет использоваться подпись и публичный ключ.
До сих пор мы рассматривали redeemScript как элемент. Но теперь все это будет интерпретировано как инструкции. Давайте возьмем пример скрипта блокировки P2PKH, для которого необходимо предоставить подпись – <signature> и публичный ключ – <public key>, который соответствует хешу публичного ключа – <public key hash> внутри <redeemScript>.



Расширение redeemScript напоминает обычную транзакцию P2PKH. Таким образом, он запускается в обычном режиме.

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

P2SH обычно удобен для таких вещей, как мультиподпись или транзакции, совместимые с SegWit. Транзакции с несколькими подписями могут быть очень большого размера, поскольку для них может потребоваться несколько ключей. До реализации Pay-to-Script-Hash отправитель должен будет перечислить все возможные публичные ключи в своем скрипте блокировки. 

Но с P2SH не имеет значения, насколько сложны условия расходования средств. Хеш redeemScript всегда имеет фиксированный размер, и затраты перекладываются на пользователей, которые хотят разблокировать скрипт блокировки.

Совместимость с SegWit – еще один случай, когда может пригодиться P2SH (мы подробно рассмотрим различия в структуре транзакции в следующем разделе). SegWit был софт-форком, который привел к изменению форматов блоков/транзакций. Поскольку для этого обновления требуется согласие, не все кошельки будут поддерживать изменения.

Не важно, переносят ли клиенты хеш скрипта SegWit в P2SH. Как и для всех транзакций этого типа, им не нужно будет знать код разблокировки redeemScript. 


Транзакции SegWit (P2WPKH и P2WSH)

Узнать больше о SegWit можно в нашей статье «Segregated Witness (SegWit). Причины и последствия обновления».
Изучая формат транзакций в SegWit, вы обнаружите отсутствие scriptSig и scriptPubKey. Теперь здесь также есть новое поле – witness. Данные, которые мы использовали для хранения в scriptSig, перемещаются на witness, а scriptSig остается пустым.

Адреса, начинающиеся с «bc1», называются SegWit-native (SegWit-совместимые начинаются с «3», поскольку они являются адресами P2SH).


Pay-to-Witness-Pubkey-Hash (P2WPKH)

Pay-to-Witness-Pubkey-Hash (P2WPKH) – это SegWit-версия P2PKH. Witness выглядит следующим образом:

<signature> <public key>

Вы заметите, что это то же самое, что и scriptSig от P2PKH. Поле scriptSig здесь пустое, а scriptPubKey выглядит следующим образом:

<OP_0> <public key hash>

Выглядит странно, да? Где опкоды для сравнения подписи, публичного ключа и хеша?

Дополнительные опкоды не отображаются здесь, так как ноды, получающие транзакцию, уже знают, что с ней делать, в зависимости от длины хеша публичного ключа – <public key hash> . Они рассчитают длину и поймут, что она должна выполняться в том же стиле, что и транзакция P2PKH.
Необновленные ноды не знают, как интерпретировать транзакцию таким образом, но это не важно. Здесь также нет witness, поэтому они читают пустой scriptSig и некоторые данные. Они оценивают его и отмечают как действительный: по их мнению, любой может использовать результат. Вот почему SegWit считается обратно совместимым софт-форком.


Pay-to-Witness-Script-Hash (P2WSH)

Хеш скрипта Pay-to-Witness-Script (P2WSH) – это новый P2SH. Если вы настолько углубились в тему, то, вероятно, уже понимаете особенности этого скрипта, но мы все равно рассмотрим его подробнее. Наш свидетель помещается в scriptSig. Например, в P2WSH, который обертывает транзакцию P2PKH. Это может выглядеть примерно так:

<signature 1> <public key>

Вот наш scriptPubKey:

<OP_0> <script hash>

Здесь действуют те же правила. Ноды SegWit считывают длину хеша скрипта и определяют, что это результат P2WSH, который оценивается аналогично P2SH. Старые ноды, в свою очередь, видят в этом выход, который может потратить кто угодно.


Резюме

В этой статье мы узнали больше о строительных блоках Биткоина. Давайте кратко резюмируем сказанное:


Тип скриптаОписание

Pay-to-Pubkey (P2PK)

Защищает средства с помощью публичного ключа

Pay-to-Pubkey-Hash (P2PKH)

Защищает средства с помощью хеша публичного ключа (т. е. адреса)

Pay-to-Script-Hash (P2SH)

Защищает средства с помощью хеша скрипта, который предоставляет получатель

Pay-to-Witness-Pubkey-Hash (P2WPKH)

Версия P2PK для SegWit

Pay-to-Witness-Script-Hash (P2WSH)

Версия P2SH для SegWit


С изучением Биткоина приходит понимание его огромного потенциала. Транзакции могут состоять из множества различных компонентов, а управляя строительными блоками, пользователи получают большую гибкость в установке условий использования средств.