Хабрахабр

Multisig-контракты и адреса в Bitcoin и Ethereum

Если вам интересно, как использовать такие адреса, то вы попросту обязаны понимать механику владения ими и прекрасно представлять себе порядок транзакций. Multisig-контракты в современных децентрализованных сетях — это мощный инструмент, который позволяет просто и надёжно защищать средства на коллективных счетах, а также проводить сделки с несколькими участниками. Для работы с такими адресами требуется участие нескольких аккаунтов.

Именно об этом внутреннем устройстве и пойдёт речь в данной статье. Несмотря на одинаковое название и схожую логику работы, внутренние алгоритмы и способы взаимодействия с адресами, защищёнными мультиподписью, довольно сильно различаются в Bitcoin и Ethereum.

В других блокчейнах multisig-доступ к криптоактивам может быть реализован совершенно иначе. Мы будем говорить о двух сетях: Bitcoin и Ethereum.

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

Когда набирается достаточно подписей, средства переводятся. Теперь каждый из трёх участников, желая создать транзакцию на вывод, должен предоставить свою подпись, подтверждающую его согласие с транзакцией. Такая логика и носит название multisig: отправка N из M подписей (M > N) для подтверждения операции.

Для децентрализованных сетей multisig — родной паттерн, так как любая валидная транзакция, отправленная на некоторый адрес, by design содержит в себе электронную подпись, созданную секретным ключом отправителя, так что практически вся нужная для multisig-адресов функциональность уже на борту большинства современных блокчейнов.

Такой multisig способен сделать удобными и безопасными многие сделки с тремя сторонами и решать задачи, когда двое участников доверяют третьему рассудить их. Наиболее интересен простейший multisig-адрес 2/3, вывод с которого возможен только по согласию двух из трёх участников. Для примера возьмём аккредитив: плательщик вносит деньги и не может забрать их, если не убедит либо банк (куда не доехали документы о покупке), либо получателя (по доброй воле) с этим согласиться. Практически любые финансовые услуги, в которых есть третья доверенная сторона, реализуются с помощью multisig 2/3. В схеме сервис банка до неприличия прост: увидев договор о покупке квартиры, он, вместо того чтобы нести ключ от ячейки в хранилище, просто отправляет approve на multisig-адрес. Получатель также не сможет забрать деньги, если либо продавец их не отдаст (по доброй воле), либо банк не сообщит, что нужные документы до него дошли и он их проверил.

Multisig, где одна из трёх подписей принадлежит организации, которая в итоге решает, была услуга оказана или нет (например, суду), означает, что суд, приняв решение в пользу одной из сторон, попросту подписывает своё решение и шлёт транзакцию, автоматически разблокируя средства для выигравшей стороны. Задачи учёта и оплаты услуг тоже тяготеют к multisig 2/3: там часто требуется, чтобы третья сторона решила, была ли оказана услуга и в каком объёме. Для сервисов B2C типа Uber или AirBnB мультисиг, хоть и неявно, основной паттерн работы: когда услуга оказана, именно подпись запроса сервисом инициирует отправку средств от клиента водителю или хозяину квартиры.

А в блокчейне всё это «без регистрации и СМС» (с точки зрения безопасности это, между прочим, довольно серьёзный тезис, если задуматься). В общем, удобно реализованный multisig — как автомат Калашникова: дёшево и сердито, подходит для использования в домашних условиях и для сборов в миллионы долларов.

Надо сказать, что внутреннее устройство multisig в них довольно сильно отличается, всё работает по-разному. Мы будем рассматривать два блокчейна — Bitcoin и Ethereum. Но не будем забегать вперёд.

Его дизайн позволяет реализовывать легковесные и при этом очень безопасные схемы получения доступа к средствам. Bitcoin на самом деле не так уж и прост, как может показаться многим. Стандартный перевод X биткоинов с адреса на адрес — это всего лишь default’ный тип транзакции, верхушка айсберга потенциальных способностей Bitcoin. Ну и добавлю, что, чисто по моему личному мнению, структуры и алгоритмы в Bitcoin отлично продуманы, оптимальны и попросту красивы. Давайте посмотрим на структуру транзакции Bitcoin и немного глубже копнём, как она работает. Дефолтная транзакция по переводу BTC с точки зрения кода представляет собой простейший multisig 1/1: чтобы воспользоваться X биткоинами с адреса а1, необходимо приложить к транзакции электронную подпись, созданную с помощью секретного ключа, который принадлежит адресу a1.

image

То есть, подтвердив своё право владеть адресом, транзакция тратит весь баланс, распределив его на несколько output’ов. В Bitcoin есть требование: все BTC, хранящиеся на некотором адресе, всегда тратятся целиком. В реале ещё и оставив разницу между input’ами и output’ами майнеру в качестве комиссии, но для данной статьи это несущественно.

Васе на адрес пришло 50 BTC. Представим ситуацию. Для этого он обязан потратить все 50 BTС. Вася хочет послать Пете 0,5 BTC. Чтобы получить «сдачу», Вася берёт один input (50 BTC) и создает два output’а:

  • 0,5 BTC на адрес Пети;
  • 49,5 BTC на один из собственных адресов.

Вася подписывает все input’ы в транзакции, причём каждый input — это отдельный bitcoin-адрес, и для каждого из них нужен соответствующий секретный ключ. Когда говорят «Вася подписывает транзакцию» — это не совсем точно для Bitcoin. Чтобы потратить 0,25 BTC, Васе придётся подписать три input’а (каждый по 0,1) и сформировать два output’a (0,25 на адрес получателя и 0,05 себе в качестве сдачи). Предположим, у Васи есть три адреса, на которые три его друга прислали по 0,1 BTC. Некоторые кошельки, например Electrum, позволяют владельцу самому выбрать стратегию для переиспользования адресов, более приватную (каждый раз новый адрес) либо более простую, но с постоянными адресами (такая схема удобней, если вы прибегаете к сложным регулярным транзакциям). Такая схема позволяет, в частности, никогда не использовать дважды один и тот же адрес и возвращать сдачу каждый раз на новый. Напоминаю, что разница между суммой input’ов и output’ов — это одновременно комиссия майнеру: просто и надёжно.

Но я специально упустил ещё несколько моментов, чтобы не усложнять общую схему. Теперь я вместо «Вася подписывает транзакцию» говорю «Вася подписывает input». Поэтому Вася, подписывая input, кроме самой подписи, предъявляет биткоиновскому Script’у ещё и свой публичный ключ, тем самым раскрывая его. Дело в том, что адреса в Bitcoin — это не public key в чистом виде, а его хеш (дважды хеш с несколькими служебными байтами и версией, подробности в статье Bitcoinwiki), то есть при обычной транзакции отправитель даже не знает публичного ключа получателя. Естественно, зная публичный ключ, можно без проблем сгенерировать Bitcoin-адрес.

Но «требования» ему предоставил тот, кто получит биткоины: это его забота, как он будет тратить свои BTC. Помимо подписи inputs, Вася ещё и ставит условие для каждого output’а, как можно потратить BTC с этого конкретного output’а. Адрес Bitcoin содержит в себе хеш от кода, который будет принимать решение, разрешить ли тратить BTC с этого адреса. Это крайне важный момент.

Вот ещё аналогия для питонистов: можно представить, что каждый output содержит lambda-функцию, которая, исполненная Петей, предоставившим ей аргументы, вернёт true либо false. Если ещё искать аналогии, то Вася к каждому output’у (который ему сообщили те, кто получит BTC) прицепляет данные, которые отвечают на вопрос «Какой код и на каких данных должен вернуть true, чтобы подписывающий считался владельцем этого output’а?». В default’ном варианте программа может взять два предоставленных Петей аргумента — подпись output’a и Петин public key — и проверить подпись. Если возвращается true, то Петя может использовать output как input для последующих транзакций. Глубже в рамках данной статьи в Script нырять необязательно, но крайне желательно. Если вернулось true, значит, Петя имеет право на output и может его потратить; следовательно, транзакция валидна. Клиенты сети, процесся транзакции, исполняют каждый script в каждой транзакции, проверяя, валидна ли она. Кстати, redeem script — это и есть тот самый smart contract, то есть формальное правило, по которому право на BTC переходит от одного адреса к другому. Если валидна — валидна и неоспорима трата биткоинов с определённого адреса.

А вот ещё одна: Bitcoin in a nutshell — Transaction. Вот первая отличная статья по этой теме, сильно рекомендую прочитать, там отлично расписан весь raw-механизм с примерами на питоне: Bitcoins the hard way: Using the raw Bitcoin protocol.

Предположим, Петя хочет, чтобы Вася просто перевёл ему BTC. Вернёмся к мультисигу 2/3, отметив, что обычную транзакцию с переводом биткоинов можно рассматривать и как multisig 1/1. Такие «традиционные» адреса в Bitcoin начинаются с цифры 1 (единицы). Петя выдаёт Васе адрес, в который зашит хеш default’ного script’а, и биткоины с этого адреса Петя сможет забрать, предоставив стандартный код проверки подписи и саму подпись. Адреса, в которые зашит «нетрадиционный» код (проверяющий multisig script), называются pay-to-script и начинаются с 3 (тройки).

Ведь это наша проблема, как мы потом будем тратить свои BTC, а не отправляющего. Bitcoin позволяет нам создать адрес, в который будет зашит хеш НАШЕГО СОБСТВЕННОГО redeem script’a. То есть, попросив перевести мне BTC на pay-to-script адрес, я обязуюсь предоставить сам скрипт потом, в транзакции, использующей данный input. Нам неважно, кто переведёт биткоины на адрес, а важно, что, когда мы захотим их потратить, нам придётся предоставить код, соответствующий адресу.

Согласно документации, для multisig используется код такого вида:

-----------------------------------------------------------
Pubkey script: OP_HASH160 <Hash160(redeemScript)> OP_EQUAL
Signature script: <sig> [sig] [sig...] <redeemScript>
-----------------------------------------------------------

(Продолжение статьи о script и multisig)

Если, к примеру, наш multisig 2/3 содержит адреса трёх участников (генерального директора, главного бухгалтера и сисадмина) и секретные ключи для подписей лежат на их компьютерах раздельно, то для формирования тратящей транзакции сисадмину придётся: Нам нужен такой script, чтобы все необходимые подписи сразу были представлены в тратящей транзакции.

  • сформировать тратящую транзакцию;
  • сформировать собственную подпись;
  • передать содержимое транзакции второму подписанту (например, сходить в бухгалтерию или к генеральному с флешкой);
  • попросить второго подписанта добавить подпись к транзакции;
  • отправить в сеть транзакцию с двумя из трёх подписей.

В этом случае вывод средств совсем «ручной», что для больших сумм весьма неплохо. Это не сильно простой способ, но он позволяет использовать в качестве одного из подписантов компьютер, который вообще отключён от сети и гарантированно не сольёт секретный ключ. XXI век, друзья, сейчас модно класть в сейф нулёвый ноутбук без подключения к сети и с единственным установленным софтом — кошельком Bitcoin.

В Ethereum специальная транзакция типа "create_contract" создаёт в сети адрес, к которому привязан код конкретного контракта. Реализация multisig в Ethereum сильно отличается от таковой в Bitcoin из-за разного дизайна внутренних алгоритмов клиента сети, но есть и параллели. Если послать туда эфир — адрес «примет его на баланс». У этого адреса также есть баланс эфира. И наоборот, если выслать с адреса эфир — тот «снимется с баланса».

Если забыть о консенсусе и о том, как ноды обновляют цепочку блоков, и рассматривать блокчейн как абстрактное хранилище, то размещение контракта можно сравнить, например, с инстанцированием объекта, то есть превращением класса в реальный объект в оперативной памяти. Я сразу накидаю несколько довольно точных аналогий для понимания весьма простой и логичной кухни. После инстанцирования объект (контракт) получает собственный адрес в «памяти», и сети известен интерфейс к нему, представленный в виде описанных в классе методов. В аналогии память — это блокчейн, а код класса — код контракта.

Методы-reader’ы, которые просто читают данные из состояния контракта, любой клиент сети выполняет локально, ибо уверен, что его копия объекта верна и подтверждена консенсусом block-producer’ов. Приходящие на адрес транзакции в такой схеме — это методы-writer’ы, они изменяют внутреннее состояние объекта контракта. Очень важно также понимать, что код контракта выполняется тогда, когда майнер «применяет» пришедшую транзакцию к существующему контракту, а в нашем варианте — попросту вызывает один из методов контракта с аргументами, вложенными в транзакцию.

Создаём контракт, в нём хардкодим адреса подписантов, когда приходят транзакции на вывод — переводим контракт последовательно в состояние ожидания нужного числа подтверждений. Ну а раз у контракта есть внутреннее состояние (поля объекта в нашей аналогии), то он может реализовывать мультисиг без необходимости присылать подписи одной транзакцией. Если речь идёт о multisig 2/3, всё может выглядеть примерно так:

  • сисадмин деплоит в сеть multisig-контракт и кладёт в него адреса генерального директора и главного бухгалтера;
  • счастливые клиенты шлют тонны эфира в контракт;
  • главный бухгалтер собирается сделать большой платёж и шлёт в контракт транзакцию с желанием перевести сколько-то эфира на внешний адрес;
  • multisig-контракт переходит в состояние «ожидаю N подтверждений», в нашем случае достаточно одного;
  • генеральный директор хочет помочь главному бухгалтеру, но забыл, в каком ноутбуке нужный секретный ключ;
  • сисадмин шлёт транзакцию — подтверждение вывода средств в контракт;
  • контракт, приняв транзакцию от сисадмина, понимает, что набралось нужное N подтверждений, и высылает эфир по заказанному главным бухгалтером адресу, одновременно возвращаясь в состояние простого приёма средств.

Это касается складирования неисполненных заявок на вывод, интервала времени ожидания вывода, удобных ограничений. В зависимости от реализации multisig-контракта много вещей можно реализовать по-разному. Например, можно разрешать выводить суммы до определённого лимита без мультиподписи и требовать подтверждения, если лимит превышен.

Самый официальный из них этот. Есть несколько вариантов кода контрактов multisig-кошельков, вы без проблем найдёте их. Существует ещё множество модификаций multisig-контрактов, а также контракты, которые представляют собой multisig, но даже не знают об этом, ибо любой контракт с несколькими ролями в философском смысле и есть multisig.

Где проще всего пощупать multisig-адреса вживую: Мы рассмотрели схему работы одного из самых важных кирпичиков для построения сложных систем контрактов и организации многосторонних сделок.

  • для Bitcoin: в кошельке Electrum есть подробная пошаговая инструкция со скриншотами, как создать и сконфигурировать multisig-адрес и как им воспользоваться. Просто поищите «Electrum multisig»;
  • для Ethereum: в стандартном кошельке «Ethereum wallet» есть раздел «Contracts», и там можно легко создать multisig. Также multisig наверняка будет во многих видах представлен на платформах для запуска стандартных контрактов, например на нашей Smartz.io сейчас уже один есть.

Как видите, на блокчейне эти схемы весьма технологичны и просты, так что используйте их в своё удовольствие и защищайте свои криптобогатства правильно.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть