Глоссарий / Протокол и обновления
OP_CHECKSIGADD
- Что такое OP_CHECKSIGADD?
- OP_CHECKSIGADD подсчитывает подписи по одному ключу за раз внутри tapscript и заменяет старый опкод мультиподписи проверкой, которую поддерживают и пакетная верификация, и будущие типы ключей.
Определено в BIP-342.
Опкод берет три элемента стека (подпись, текущий счетчик и публичный ключ) и возвращает счетчик обратно без изменений либо увеличенным на единицу. BIP-342 назначил ему номер опкода 186 и одновременно отключил OP_CHECKMULTISIG, поэтому любая мультиподпись в скрипте Taproot пишется именно так. Практическая выгода в том, что пороговая политика больше не упирается в 20 ключей.
Как это работает
Порог в tapscript представляет собой цепочку проверок подписей, между которыми передается общий счет, и сравнение в конце.
В развернутом виде схема два из трех выглядит так: поместить в стек первый публичный ключ, OP_CHECKSIG, поместить второй, OP_CHECKSIGADD, поместить третий, OP_CHECKSIGADD, поместить 2, OP_NUMEQUAL. Выполнение идет слева направо. OP_CHECKSIG оставляет в стеке 1 или 0, а каждый OP_CHECKSIGADD извлекает публичный ключ, извлекает этот текущий итог, извлекает предложенную для этого ключа подпись и возвращает итог, прибавив 1, если подпись прошла проверку. Финальное сравнение решает, набралось ли нужное число.
Подписант, который не участвует, вместо подписи передает пустую строку байтов. Опкод видит ее, считает позицию неподписанной и возвращает счет без изменений, что стоит одного байта в свидетеле. В этом весь механизм, и он устраняет фиктивный элемент стека, который требовался для OP_CHECKMULTISIG из-за ошибки на единицу в исходной реализации.
Из сопоставления одного ключа с одной подписью следуют два последствия. Верификация становится пакетной: узел может собрать все подписи Schnorr из блока в одну агрегированную проверку вместо последовательного перебора ключей-кандидатов, и это значительная часть ускорения валидации в Taproot. А число ключей ограничено бюджетом валидации tapscript, который расходует 50 весовых единиц на каждую проверенную подпись, а не жестким потолком, записанным в опкод.
Где это встречается
OP_CHECKSIGADD встречается в любом скрипте Taproot, которому нужно больше одного подписанта, а в кошельковом программном обеспечении это обычно означает дескриптор, а не сырой скрипт.
Bitcoin Core предоставляет его как фрагменты multi_a и sortedmulti_a внутри дескриптора tr(), поэтому кворум из трех ключей записывается как tr(internalkey,multi_a(2,KEY1,KEY2,KEY3)), а программа сама формирует цепочку опкодов. Miniscript обращается с ним так же, а аппаратные устройства, поддерживающие политики Taproot, подписывают такие скрипты, не нуждаясь в том, чтобы заранее знать их структуру.
Конструкция меняет и то, как можно строить кворум. Вместо одного листа, содержащего все три ключа, taptree может содержать три отдельных листа по два ключа, по одному на каждую возможную пару, и при расходе раскрывается только та пара, которая действительно подписала. Это дороже по числу листьев скрипта и экономит один публичный ключ в свидетеле, и это настоящее улучшение приватности по сравнению с публикацией полного набора ключей при каждом расходе.
OP_CHECKSIGADD vs OP_CHECKMULTISIG
OP_CHECKSIGADD и OP_CHECKMULTISIG оба подсчитывают подписи по набору ключей, и только один из них работает в листе Taproot. OP_CHECKMULTISIG остается вполне действительным в скриптах legacy и SegWit версии 0, поэтому мультиподпись bc1q, которую люди используют годами, не затронута и не требует миграции.
Внутри tapscript OP_CHECKMULTISIG и OP_CHECKMULTISIGVERIFY отключены полностью. Помимо места применения важны три различия: старый опкод ограничивает скрипт 20 публичными ключами, а у нового такого числа нет; старый требует дополнительного фиктивного элемента в стеке, который BIP-147 позже обязал оставлять пустым как правило консенсуса; а старый нельзя проверять пакетно, потому что проверяющий не знает заранее, какому ключу принадлежит каждая подпись.