Блокчейн

Bitcoin Script: программируемые деньги

Ты отправляешь Bitcoin. Кажется, что транзакция - это просто перевод с одного адреса на другой, как банковский платёж. Но внутри каждой транзакции скрыта мини-программа - скрипт на Forth-подобном языке, который буквально говорит: «Докажи, что ты имеешь право потратить эти монеты». Без циклов, без переменных, с одним только стеком - и этого достаточно, чтобы построить целую платёжную сеть на миллиарды долларов.

  • **Bitcoin Lightning Network** - сеть мгновенных платежей с пропускной способностью миллионы транзакций в секунду, построенная целиком на комбинации hashlocks и timelocks в Bitcoin Script
  • **Multisig-кошельки** защищают более 50 млрд корпоративных и институциональных средств. Биржи вроде Coinbase хранят клиентские средства в multisig 2-of-3, где один ключ - у клиента
  • **Escrow без посредника** - через 2-of-3 multisig покупатель и продавец совершают сделку, где арбитр вмешивается только при споре, но не может украсть средства в одиночку

Предварительные знания

  • Bitcoin: the UTXO model
  • Digital Signatures: ECDSA and EdDSA

Стековая машина: Forth-подобный язык Bitcoin

В предыдущем уроке мы узнали, что каждый UTXO «заперт» скриптом. Но что за язык используется для этих скриптов? Ответ удивляет: Сатоши Накамото создал **собственный** язык программирования - **Bitcoin Script**.

Bitcoin Script - это **стековый язык**, вдохновлённый Forth (1970). Вместо переменных и функций здесь единственная структура данных - **стек** (stack). Каждая инструкция либо кладёт данные на вершину стека, либо снимает их оттуда, выполняет операцию и кладёт результат обратно.

Каждая Bitcoin-транзакция содержит два скрипта: - **scriptPubKey** (locking script) - «замок» на UTXO. Задаёт условие: *кто может потратить?* - **scriptSig** (unlocking script) - «ключ». Предоставляет данные, удовлетворяющие условию

При валидации транзакции нода **конкатенирует** scriptSig и scriptPubKey и выполняет результат как единую программу. Если после выполнения на вершине стека остаётся **TRUE** (ненулевое значение) - транзакция валидна.

**Bitcoin Script намеренно НЕ является Turing-complete.** В нём нет циклов, рекурсии и доступа к состоянию блокчейна. Это сделано сознательно: Turing-complete язык позволил бы создавать бесконечные циклы, что парализует сеть. Каждый скрипт **гарантированно завершается** за конечное время. Это ограничение - не недостаток, а защитный механизм.

**Forth** - язык, созданный Чарльзом Муром в 1970 году для управления телескопами. Его стековая архитектура оказалась настолько надёжной, что её использовали в космических зондах (Philae, Rosetta) и - спустя 38 лет - в Bitcoin. Минимализм Forth идеально подошёл для денег: чем проще язык, тем меньше поверхность атаки.

Почему Bitcoin Script не поддерживает циклы и рекурсию?

Опкоды: инструкции стековой машины

Bitcoin Script состоит из **опкодов** (operation codes) - элементарных инструкций, каждая из которых выполняет одну операцию со стеком. Всего определено около **100 опкодов**, но активно используется лишь пара десятков.

ОпкодДействиеСтек до → после
OP_DUPДублирует верхний элемент стека[a] → [a, a]
OP_HASH160RIPEMD160(SHA256(x)) - двойное хеширование[x] → [hash160(x)]
OP_EQUALСравнивает два элемента, кладёт TRUE/FALSE[a, b] → [a == b]
OP_EQUALVERIFYКак OP_EQUAL, но прерывает скрипт если FALSE[a, b] → [] (или FAIL)
OP_CHECKSIGПроверяет ECDSA-подпись[sig, pubKey] → [TRUE/FALSE]
OP_CHECKMULTISIGПроверяет m-of-n подписей[sig1..sigM, M, pub1..pubN, N] → [TRUE/FALSE]
OP_RETURNПомечает output как «неспрашиваемый» (provably unspendable)- (данные после OP_RETURN игнорируются)

Рассмотрим пошаговое выполнение простого скрипта: **проверка, что предоставленное значение хешируется в ожидаемый хеш** (hashlock).

**OP_RETURN** - специальный опкод, позволяющий записать **произвольные данные** в блокчейн (до 80 байт). Поскольку output с OP_RETURN неспрашиваем, ноды не хранят его в UTXO-наборе. Этот опкод используют для записи хешей документов, сообщений и метаданных протоколов вроде Ordinals.

**Категории опкодов:** - **Константы**: OP_0, OP_1, ... OP_16, OP_PUSHDATA - кладут данные на стек - **Управление**: OP_IF, OP_ELSE, OP_ENDIF - ветвление (но не циклы!) - **Стек**: OP_DUP, OP_DROP, OP_SWAP - манипуляции со стеком - **Арифметика**: OP_ADD, OP_SUB, OP_EQUAL - вычисления - **Криптография**: OP_HASH160, OP_SHA256, OP_CHECKSIG - хеширование и подписи - **Timelocks**: OP_CHECKLOCKTIMEVERIFY, OP_CHECKSEQUENCEVERIFY - условия по времени

Что произойдёт при выполнении: PUSH <data> OP_HASH160 OP_HASH160?

P2PKH: самый распространённый тип транзакции

**Pay-to-Public-Key-Hash (P2PKH)** - классический тип Bitcoin-транзакции, который использовался в подавляющем большинстве переводов до появления SegWit. Адреса P2PKH начинаются с **1** (например, `1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa` - адрес Сатоши).

Логика P2PKH: «Чтобы потратить этот UTXO, предоставь публичный ключ, хеш которого совпадает с указанным, **и** валидную подпись этим ключом». Два уровня защиты: знание ключа + владение секретом.

**Зачем хешировать публичный ключ?** Почему не просто OP_CHECKSIG с публичным ключом? 1. **Компактность**: hash160 = 20 байт, а публичный ключ (сжатый) = 33 байта. При миллионах UTXO это экономия 2. **Постквантовая защита**: пока UTXO не потрачен, публичный ключ неизвестен. Квантовый компьютер не может атаковать то, чего не видит. Ключ раскрывается только в момент траты 3. **Дополнительный слой**: даже если ECDSA взломан, злоумышленнику нужно ещё обратить HASH160

**Адрес Bitcoin** - это Base58Check-кодировка hash160 публичного ключа. Base58 исключает символы `0`, `O`, `I`, `l`, чтобы избежать путаницы при ручном вводе. Префикс `0x00` для mainnet даёт адреса, начинающиеся с **1**.

В P2PKH-скрипте OP_DUP дублирует публичный ключ. Зачем нужна копия, если ключ уже на стеке?

P2SH: хеш скрипта вместо самого скрипта

P2PKH прекрасно работает для простых переводов: один ключ, одна подпись. Но что если условие траты сложнее? Например, multisig 2-of-3 требует скрипт длиной **~250 байт**. И этот скрипт хранится в **каждом UTXO** - отправитель платит за длину скрипта получателя.

**Pay-to-Script-Hash (P2SH)**, предложенный в **BIP-16** (2012), решает эту проблему компактно: вместо самого скрипта в UTXO хранится только его **хеш**. Полный скрипт предоставляется позже - в момент траты.

P2SH-адреса начинаются с **3** (например, `3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy`). Процесс валидации двухэтапный:

**P2SH - универсальная обёртка.** Внутри redeemScript может быть что угодно: multisig, timelock, hashlock, или их комбинация. Отправителю не нужно знать детали - достаточно хеша. Это разделение ответственности: отправитель знает *куда*, получатель определяет *как*.

Компания хочет использовать multisig 2-of-3 для получения платежей. В чём главное преимущество P2SH перед наивным multisig-скриптом в UTXO?

Multisig: m-of-n подписей и знаменитый баг

**OP_CHECKMULTISIG** - опкод, проверяющий, что **m из n** предоставленных подписей валидны. Это основа корпоративных кошельков, escrow-сервисов и совместного управления средствами.

**Знаменитый off-by-one баг.** Заметили `OP_0` в начале scriptSig? Это **dummy-элемент**, который не несёт смысла, но обязателен. OP_CHECKMULTISIG содержит ошибку в оригинальном коде Сатоши: он снимает со стека на **один элемент больше**, чем нужно. Баг обнаружили слишком поздно - исправление сломало бы все существующие транзакции (hard fork). Решение: стандарт **BIP-11** требует, чтобы первым элементом был OP_0. Этот баг живёт в Bitcoin уже 17 лет и, вероятно, переживёт всех нас.

Применение multisig в реальном мире

Три сценария, три конфигурации

1. Корпоративный кошелёк (2-of-3): CEO + CFO = штатный платёж CEO + CTO = экстренный платёж Один ключ украден → средства в безопасности 2. Escrow-сервис (2-of-3): Покупатель + Продавец = сделка без посредника Покупатель + Арбитр = возврат при споре Продавец + Арбитр = подтверждение доставки → Арбитр не может украсть средства в одиночку 3. Семейный траст (3-of-5): 5 членов семьи, для траты нужно согласие большинства. Защита от потери 2 ключей и от сговора меньшинства.

**Ограничения OP_CHECKMULTISIG:** - Максимум **20 публичных ключей** (OP_20 - максимальное значение в Script) - Проверка **линейная**: каждая подпись сравнивается с каждым ключом по порядку. Порядок подписей должен совпадать с порядком ключей - Раскрывает **все** публичные ключи участников - даже тех, кто не подписывал. Taproot (Schnorr + MAST) решает эту проблему приватности

**Gnosis Safe** (ныне Safe) - самый популярный multisig-кошелёк Ethereum, хранящий более **$100 млрд** активов. В Bitcoin аналогичные решения - **Electrum** (2-of-3) и **Unchained Capital** (collaborative custody). Биржа **Bitfinex** в 2016 году потеряла $72 млн из-за уязвимости в своей multisig-конфигурации.

Bitcoin Script - примитивный язык, который не позволяет создавать сложные условия траты, поэтому для «умных контрактов» нужен Ethereum

Bitcoin Script поддерживает **ветвление** (OP_IF/OP_ELSE), **timelocks** (OP_CHECKLOCKTIMEVERIFY), **hashlocks** и **multisig** - комбинация этих примитивов позволяет строить сложные протоколы. **Lightning Network** - полноценная платёжная сеть второго уровня - построена целиком на Bitcoin Script (HTLC = hashlock + timelock). **Taproot** (2021) добавил деревья скриптов (MAST) и Schnorr-подписи, значительно расширив возможности.

Путаница возникает из-за отсутствия Turing-completeness. Но Turing-completeness - не показатель мощности для финансовых контрактов. Bitcoin Script намеренно ограничен, чтобы гарантировать безопасность и предсказуемость. Большинство реальных финансовых контрактов (escrow, timelocks, multisig, payment channels) реализуемы без циклов. Ограниченность - это feature, а не bug.

Почему scriptSig для multisig-транзакции начинается с OP_0 (dummy-элемента)?

Итоги

  • **Bitcoin Script** - стековый Forth-подобный язык, намеренно не Turing-complete. scriptSig (ключ) + scriptPubKey (замок) = программа. Если стек завершается с TRUE - транзакция валидна
  • **Опкоды** - ~100 инструкций: OP_DUP, OP_HASH160, OP_CHECKSIG и другие. Каждый опкод выполняет одну атомарную операцию со стеком
  • **P2PKH** - «покажи публичный ключ + подпись». Адреса начинаются с **1**. Семь шагов: от подписи на стеке до TRUE на вершине
  • **P2SH** - хеш скрипта вместо самого скрипта. Уменьшает UTXO с ~250 до 23 байт. Адреса начинаются с **3**
  • **Multisig** (OP_CHECKMULTISIG) - m-of-n подписей для корпоративных кошельков и escrow. Тот самый dummy-элемент OP_0 - баг из 2009 года, который переживёт нас всех. А вся эта система, работающая без циклов и переменных, оказалась достаточно мощной, чтобы на ней построили Lightning Network с миллионами транзакций в секунду

Связанные темы

Bitcoin Script связывает модель UTXO с продвинутыми протоколами второго уровня:

  • Bitcoin: модель UTXO — Каждый UTXO «заперт» скриптом (scriptPubKey). Понимание Script невозможно без понимания UTXO - скрипт определяет *условие траты* каждого неизрасходованного выхода
  • Lightning Network — Payment channels построены на HTLC (Hash Time-Locked Contracts) - комбинации OP_HASH160 (hashlock) и OP_CHECKLOCKTIMEVERIFY (timelock) из Bitcoin Script
  • Taproot и Schnorr — Taproot (BIP-340/341/342) заменяет OP_CHECKMULTISIG на Schnorr-подписи и добавляет MAST (Merklized Abstract Syntax Trees) - деревья скриптов, где раскрывается только использованная ветка

Вопросы для размышления

  • Bitcoin Script намеренно не Turing-complete, а Ethereum EVM - Turing-complete (с ограничением через gas). Какой подход безопаснее для хранения ценности, и почему оба имеют право на существование?
  • Off-by-one баг в OP_CHECKMULTISIG живёт в Bitcoin с 2009 года. Как бы вы поступили, будучи core-разработчиком: исправить через hard fork (рискуя расколом сети) или оставить навсегда? Какие ещё примеры «вечных» багов в критических системах вы знаете?
  • P2SH переносит стоимость хранения скрипта с отправителя на получателя. Справедливо ли это экономически? Как эта идея «отложенного раскрытия» применяется в других областях (например, в zero-knowledge proofs)?

Связанные уроки

  • fl-05-regex
Bitcoin Script: программируемые деньги

0

1

Войти