Глоссарий / Справочник разработчика
OP_VERIFY
- Что такое OP_VERIFY?
- OP_VERIFY снимает верхний элемент стека и прерывает весь скрипт, если этот элемент не является истинным, превращая любую проверку в Bitcoin Script в жесткое требование.
Байт 0x69 представляет собой утверждение общего назначения этого языка. Script умеет ветвиться с помощью OP_IF, но у него нет способа вызвать ошибку и нет возвращаемого значения, кроме того, что уцелеет в стеке, поэтому контракт настаивает на условии так: вычисляет ответ и передает его этой инструкции. Практически каждый платеж за всю историю прошел через нее, обычно скрытую внутри совмещенного кода операции.
Как это работает
OP_VERIFY удаляет один элемент из стека и задает один вопрос: истинно ли это?
Истина здесь определяется на уровне байтов, а не чисел. Пустой элемент ложен. Ложна и последовательность нулевых байтов, ложен и отрицательный ноль, последовательность нулей, заканчивающаяся на 0x80. Все остальное считается истинным, включая хеш длиной 20 байт или публичный ключ длиной 33 байта. Если элемент ложен, скрипт немедленно завершается, а вход, содержащий его, недействителен, что делает недействительной и транзакцию вокруг него. Если элемент истинен, в стек ничего не возвращается, и вычисление переходит к следующему коду операции.
Именно это поведение позволяет скрипту проверить несколько вещей по очереди. Скрипт завершается успешно, только когда в конце остается ровно одно истинное значение, поэтому без утверждений каждое условие пришлось бы свернуть в один итоговый логический результат. С ними контракт может потребовать таймлок, затем потребовать прообраз хеша, затем потребовать подпись, завершаясь неудачей при первом же несовпадении.
У четырех кодов операций утверждение встроено, и существуют они только для экономии одного байта: OP_EQUALVERIFY, OP_CHECKSIGVERIFY, OP_CHECKMULTISIGVERIFY и OP_NUMEQUALVERIFY. Поскольку они покрывают обычные случаи, отдельный 0x69 в mainnet встречается редко.
Два кода операций носят VERIFY в названии и ведут себя совсем иначе. OP_CHECKLOCKTIMEVERIFY из BIP65 и OP_CHECKSEQUENCEVERIFY из BIP112 были добавлены мягким форком на прежде бессмысленные коды операций, не делающие ничего, поэтому, чтобы старые узлы остались довольны, они должны были оставить стек точно таким, каким его застали. Оба проверяют верхний элемент и завершаются неудачей, когда условие не выполнено, но ни один его не потребляет, поэтому в реальных скриптах за ними всегда идет OP_DROP.
Где это встречается
Утверждения сосредоточены там, где скрипту нужно доказать больше одной вещи.
Простое хранилище с блокировкой по времени читается так: публичный ключ, OP_CHECKSIGVERIFY, задержка, например 144 блока (примерно сутки), затем OP_CHECKSEQUENCEVERIFY. Скрипты обязательств и HTLC в Lightning устроены так же: проверка прообраза и проверка подписи сцеплены так, что ни одна из них сама по себе не достаточна, чтобы переместить деньги. Расходы по скриптовому пути Taproot сильно опираются на этот шаблон, поскольку tapscript заменил OP_CHECKMULTISIG на OP_CHECKSIGADD и использует формы verify, чтобы политики с несколькими ключами оставались компактными.
Miniscript, компилятор политик, которым несколько настольных кошельков уже описывают условия расходования в читаемой форме, создает эти коды операций за вас. Это самый простой способ увидеть, как порождаются утверждения, не пиша сырой скрипт вручную.
OP_VERIFY vs OP_RETURN
OP_VERIFY и OP_RETURN оба завершают скрипт неудачей, но только один из них сначала задает вопрос. OP_VERIFY завершается неудачей, когда переданное ему значение ложно, поэтому корректный расход проходит мимо, не оставляя следа. OP_RETURN, байт 0x6a, завершается неудачей всякий раз, когда выполняется, вообще без всякого условия, и именно поэтому его используют для создания выходов, которые никогда нельзя потратить, и для прикрепления данных к транзакции. Скрипт, который умирает на первом, получил что-то неверное. Скрипт, который умирает на втором, изначально не предназначался для расходования.