Я работал над проектом Rust-Miniscript во время своей интернатуры Summer of Bitcoin 2023. Этот пост в блоге посвящен тому, что на самом деле представляет собой модуль планирования, зачем он нам нужен и как он решает проблемы в мире биткойнов. Мы будем постепенно переходить к работе, которую я проделал на протяжении всей стажировки, по улучшению модуля «Планирование».
Прежде чем мы углубимся в детали модуля «Планирование», я хотел бы добавить информацию обо всех частях проектов и мое понимание их. Статья разделена на разделы, и вы можете перейти к разделам, которые вас больше всего интересуют. Чтение всех разделов должно помочь новичкам в разработке биткойнов дать более четкое представление. Давайте начнем.
Биткойн-скрипты
Bitcoin Script — это простой язык программирования на основе стека, используемый в протоколе Биткойн для определения условий расходов для транзакций Биткойн. Это неотъемлемая часть того, как блокчейн Биткойна обеспечивает безопасность и контроль над движением средств. Биткойн-скрипт позволяет пользователям указывать правила, которые должны быть соблюдены, чтобы потратить средства, заблокированные на определенном выходе (также известном как «UTXO» или неизрасходованный вывод транзакции).
Каждая транзакция Биткойн включает в себя один или несколько входов и выходов. Входные данные транзакции ссылаются на предыдущие выходные данные (UTXO) и предоставляют необходимые данные для удовлетворения условий расходов, определенных сценарием блокировки выходных данных. Скрипт блокировки, также известный как «scriptPubKey», определяет правила, которым необходимо соответствовать для расходования средств. Входные данные включают отдельный сценарий под названием «scriptSig», который предоставляет данные, необходимые для удовлетворения условий сценария блокировки.
Операции Bitcoin Script включают простые математические операции, побитовые операции, криптографические операции и условное ветвление. Его сложность намеренно разработана с целью обеспечения безопасности и предотвращения злонамеренного или непреднамеренного поведения. Этот выбор конструкции призван минимизировать потенциальную поверхность атаки и предотвратить уязвимости, которые могут поставить под угрозу целостность сети Биткойн.
ПСБТ
Частично подписанная биткойн-транзакция (PSBT) — это стандартизированный формат, который позволяет нескольким сторонам совместно создавать и подписывать биткойн-транзакции, не раскрывая конфиденциальные закрытые ключи. Он предназначен для облегчения автономной подписи, настройки нескольких подписей и интеграции аппаратного кошелька, что делает процесс создания и подписания биткойн-транзакций более безопасным и гибким.
PSBT позволяет отделить процесс создания транзакции от процесса подписания, что особенно полезно для сценариев, когда стороны хотят сохранить безопасность своих личных ключей при совместной работе над созданием транзакции.
Мини-скрипт
Miniscript — это язык сценариев, предназначенный для упрощения создания и анализа биткойн-скриптов. Традиционные сценарии Биткойна могут быть сложными и трудными для понимания. Miniscript стремится упростить этот процесс, предоставляя более структурированный и удобочитаемый способ выражения условий расходов.
Ключевые особенности Минискрипта
Дескрипторы: Дескрипторы используются для определения условий расходования результатов более понятным и абстрактным способом, что упрощает создание сложных сценариев и управление ими.
Политика: Политика — это механизм в Rust-Miniscript, который определяет, может ли дескриптор быть удовлетворен или нет. Он включает в себя оценку того, были ли выполнены данные условия расходования, и если да, то разрешение расходования соответствующего UTXO. Этот уровень абстракции еще больше повышает читабельность и удобство использования Rust-Miniscript, делая его доступным даже для тех, кто не имеет глубокого понимания сценариев Биткойна.
Компилятор:Компилятор устраняет разрыв между удобочитаемыми дескрипторами и машинно-исполняемыми сценариями. Он принимает дескриптор в качестве входных данных и генерирует соответствующий биткойн-скрипт, оптимизируя выбор и сборку кода операции. Этот шаг сводит к минимуму вероятность ошибки и гарантирует эффективность и безопасность полученных сценариев.
Ставка комиссии за транзакцию биткойнов
Каждая транзакция биткойнов проверяется сетью узлов, и для обработки транзакции в блокчейне требуется комиссия за транзакцию.
В случае многосторонних транзакций важно согласовать размер комиссии за транзакцию, чтобы предотвратить DOS-атаки. Чтобы решить эту проблему,rust-miniscript реализовал анализ абсолютного наихудшего случая для удовлетворения весов и использует его для расчета комиссий.
Необходимость модуля планирования
Поскольку мы уже знаем, нам необходимо рассчитать ставку комиссии. Теперь предположим, что для транзакций с мультиподписью вам понадобятся 2 из 3 ключей, и вы достаточно уверены, что одного из ключей точно нет. Тогда вы можете избежать расчета комиссии по этому ключу. Но в худшем случае для k из n кошельков с мультиподписными транзакциями были созданы такие, что по умолчанию требовалось n ключей, что означает
вес удовлетворенности = n*вес каждого ключа
Модуль планирования играет жизненно важную роль в решении проблемы перерасхода средств в случае недоступности активов. Это позволяет вам выбрать путь расходования. Таким образом, теперь вы можете выбирать только kключей вместо всех n. Это помогает снизить размер комиссии до k*веса каждого ключа. (k≤n)
Что такое модуль планирования
План представлен определенным путем расходов в дескрипторе (правилах вывода транзакции расходов). Это способ, с помощью которого мы можем проанализировать выбор траектории расходов без фактического предоставления каких-либо подписей или других свидетельских данных.
Чтобы создать план расходов, нам необходимо предоставить дескриптор вместе с ключами или хэшами предварительных изображений, а также ограничениями абсолютной и относительной блокировки времени. Здесь мы также можем узнать, какое подмножество доступных ключей и хешей предварительных изображений нам действительно нужно будет использовать. Это полезно для создания транзакции Биткойн.
Как только мы получим необходимые подписи, предварительные изображения хэша и т. д., план расходов может создать свидетеля или script_sig для входных данных. Который можно использовать для подписи транзакции и вывода средств, определенных дескриптором.
Вместо создания Witness [подписей, хэш-прообразов для проверки владения] создаются Witness Templates, которые содержат заполнители вместо фактической подписи и прообраза. Это поможет более точно определить вес свидетеля перед созданием самого свидетеля.
Весь рабочий процесс с использованием модуля Планировщик должен выглядеть так, как показано ниже.
https://github.com/rust-bitcoin/rust-miniscript/pull/481
Пример для понимания минискрипта и планирования
Одной из частей моей работы было написание примеров учебных пособий для разработчиков, которым нужно было использовать Rust-Miniscript в качестве контейнера для своего бэкэнда кошелька. Чтобы объяснить весь рабочий процесс, связанный с Miniscript и модулем планирования, я пишу примерный пример ниже.
Объяснение: Мы пытаемся написать политику, которая
клавиша A И [клавиша B ИЛИ клавиша C ИЛИ клавиша D]
Политика расходов: and(pk(A),or(pk(B),or(pk(C),pk(D))))
Вывод минискрипта: and_v(or_c(pk(B),vc:or_i(pk_h(C),pk_h(D))),pk(A))
Биткойн-скрипт:
<B> OP_CHECKSIG OP_NOTIF OP_IF OP_DUP OP_HASH160 <HASH160(C)> OP_EQUALVERIFY OP_ELSE OP_DUP OP_HASH160 <HASH160(D)> OP_EQUALVERIFY OP_ENDIF OP_CHECKSIGVERIFY OP_ENDIF <A> OP_CHECKSIG
Модуль оценки удовлетворенности и планирования:
Рассматривая эти ключи как часть дескриптора wsh, который использует подписи ECDSA длиной 72 байта. Выполнение этого сценария обойдется нам примерно в 4*72 = 288 байт. Но могут быть и другие издержки, связанные с продвижением или неудовлетворением сценария.
Вышеописанное относится к анализу наихудшего случая.
Теперь, благодаря модулю планирования, мы можем выбрать свой путь. Например, возможные пути в приведенной выше политике будут такими:
- ключ A, ключ B (144 байта)
- ключ A, ключ C (144 байта)
- ключ A, ключ D (144 байта)
- ключ A, ключ B, ключ C (216 байт)
- ключ A, ключ B, ключ D (216 байт)
- ключ A, ключ C, ключ D (216 байт)
- ключ A, ключ B, ключ C, ключ D (288 байт)
Очевидно, что мы можем выбрать ключи с наименьшим количеством активов, которые в данном случае представляют собой комбинацию двух ключей стоимостью 144 байта. И мы можем почти вдвое уменьшить ставку комиссии, выбрав этот путь расходов и разработав вход PSBT.
Именно это возможно с помощью модуля планирования, выбрав конкретный путь расходов.
Поскольку работа велась в сотрудничестве с разработчиками BDK, мы ждем объединения #481, и мой пиар основан на их работе. Ниже приведен PR, который добавляет API для получения всех возможных запланированных путей расходов и подсчета активов в линейном времени для проверки возможностей атак DOS.
Улучшения в модуле планирования
Я работал над логикой получения всех комбинаций планов для данного дескриптора и политики. Итак, в приведенном выше примере у нас есть 7 возможных путей расходования средств. Я написал метод под названием get_all_assets, который рекурсивно проверяет различные элементы минискрипта и возвращает вектор активов, который можно создать на основе путей политики.
https://github.com/rust-bitcoin/rust-miniscript/pull/559
Чтобы защититься от DOS-атаки, нам необходимо проверить общее количество планов. Если оно превысит определенный порог, мы просто отклоним дескриптор. Это важно, потому что в случае, если алгоритм попытается спланировать удовлетворение для всех возможностей, это в конечном итоге приведет к увеличению перегрузки в сети, а мы просто не хотим, чтобы это произошло.
У нас есть две разные функции для получения активов и подсчета всех активов, поскольку получение активов происходит с экспоненциальной сложностью, но простой подсчет активов должен занять меньше времени, если мы уже можем вычислить комбиноматрическую формулировку для элементов минискрипта.
Например: AND(A,B) даст только 1 решение [(ключ A, ключ B)]
OR(A,B) даст 2 решения [(ключ A),(ключ B)]
multi (k,A1,A2,A3,…,An) даст nCk возможностей.
Проблемы
Когда минискрипт пытается финализировать PSBT, он не имеет полного дескриптора (который содержал фрагмент pkh()) и вместо этого прибегает к анализу необработанного сигнала сценария, который внутренне преобразуется в «expr_raw_pkh». Мы представили метод substitute_raw_pkh для преобразования expr_raw_pkh в pkh, просматривая доступные ключи и их хэши, хранящиеся внутри хеш-карты ввода PSBT. Если найден ключ, соответствующий данному хешу, он обновится до pkh.
Для подписей ECDSA в «Деривациях BIP32» мы получаем все ключи, включенные в данный дескриптор. А для Шорра Сигса в Taproot мы попадаем в `tap_key_origins`. Здесь нам нужны все ключи, чтобы вынести вердикт, хотим ли мы их удовлетворить или не удовлетворить. Эти ключи сопоставляются с их хэшами ключей, и мы просто заменяем значения expr_raw_pkh на PKH(key), чтобы вернуть их в нехешированную форму. Этот обратный перевод преобразует дескриптор, и мы можем сохранить его свойство обратно и финализировать его для транзакции.
https://github.com/rust-bitcoin/rust-miniscript/pull/557
https://github.com/rust-bitcoin/rust-miniscript/pull/589
Заключение
Модуль планирования в Rust-Miniscript революционизирует способ создания биткойн-скриптов разработчиками, предлагая высокоуровневый, понятный человеку подход к определению условий расходов. Эта абстракция повышает безопасность, читаемость и эффективность, что делает ее бесценным инструментом для разработки сложных сценариев расходов, сводя при этом к минимуму риск ошибок сценариев. Включение модуля планирования Rust-Miniscript в ваши проекты может значительно упростить процесс создания сложных биткойн-транзакций.
Если вы заинтересованы в максимальном раскрытии потенциала Rust-Miniscript, изучение модуля планирования станет важным шагом на пути к тому, чтобы стать опытным разработчиком биткойн-скриптов.
Пожалуйста, рассмотрите возможность внести свой вклад в этот замечательный проект. Вы не должны быть ограничены ограниченными знаниями о биткойнах. Если вы знаете основы программирования и немного разбираетесь в языке Rust, вы всегда можете найти некоторые удобные для новичков проблемы и сделать их подходящими для проекта. 😉