Перейти к основному контенту

EIP-1271: Подписание и проверка подписей смарт-контрактов

eip-1271
смарт-контракты
проверка
подписание
Средний уровень
Нейтан Х. Люн
12 января 2023 г.
6 минут на чтение

Стандарт EIP-1271 (opens in a new tab) позволяет смарт-контрактам проверять подписи.

В этом руководстве мы дадим обзор цифровых подписей, истории создания EIP-1271 и конкретной реализации EIP-1271, используемой Safe (opens in a new tab) (ранее Gnosis Safe). Все это вместе может послужить отправной точкой для реализации EIP-1271 в ваших собственных контрактах.

Что такое подпись?

В данном контексте подпись (точнее, «цифровая подпись») — это сообщение плюс некое доказательство того, что сообщение исходит от конкретного человека/отправителя/адреса.

Например, цифровая подпись может выглядеть так:

  1. Сообщение: «Я хочу войти на этот сайт с помощью моего кошелька Эфириума».
  2. Подписант: Мой адрес — 0x000…
  3. Доказательство: Вот доказательство того, что я, 0x000…, действительно создал все это сообщение (обычно это что-то криптографическое).

Важно отметить, что цифровая подпись включает в себя как «сообщение», так и саму «подпись».

Почему? Например, если бы вы дали мне контракт на подпись, а затем я отрезал бы страницу с подписью и вернул вам только свои подписи без остальной части контракта, контракт был бы недействителен.

Точно так же цифровая подпись ничего не значит без связанного с ней сообщения!

Зачем нужен EIP-1271?

Чтобы создать цифровую подпись для использования в блокчейнах на базе Эфириума, вам, как правило, нужен секретный приватный ключ, который больше никто не знает. Именно это делает вашу подпись вашей (никто другой не сможет создать такую же подпись без знания секретного ключа).

Ваш аккаунт Эфириума (т. е. ваш внешний аккаунт/EOA) имеет связанный с ним приватный ключ, и именно этот приватный ключ обычно используется, когда веб-сайт или децентрализованное приложение (dapp) запрашивает у вас подпись (например, для «Войти с помощью Эфириума»).

Приложение может проверить подпись (opens in a new tab), которую вы создаете с помощью сторонней библиотеки, такой как ethers.js, не зная вашего приватного ключа (opens in a new tab), и быть уверенным, что именно вы создали эту подпись.

Фактически, поскольку цифровые подписи EOA используют криптографию с открытым ключом, они могут генерироваться и проверяться офчейн! Именно так работает безгазовое голосование в ДАО — вместо отправки голосов ончейн, цифровые подписи могут создаваться и проверяться офчейн с использованием криптографических библиотек.

В то время как аккаунты EOA имеют приватный ключ, аккаунты смарт-контрактов не имеют никакого приватного или секретного ключа (поэтому «Войти с помощью Эфириума» и т. д. не могут изначально работать с аккаунтами смарт-контрактов).

Проблема, которую призван решить EIP-1271: как мы можем определить, что подпись смарт-контракта действительна, если у смарт-контракта нет «секрета», который он мог бы включить в подпись?

Как работает EIP-1271?

У смарт-контрактов нет приватных ключей, которые можно использовать для подписания сообщений. Так как же мы можем определить, является ли подпись подлинной?

Что ж, одна из идей заключается в том, что мы можем просто спросить смарт-контракт, является ли подпись подлинной!

EIP-1271 стандартизирует эту идею «спрашивания» смарт-контракта о том, действительна ли данная подпись.

Контракт, реализующий EIP-1271, должен иметь функцию под названием isValidSignature, которая принимает сообщение и подпись. Затем контракт может запустить некоторую логику проверки (спецификация не требует здесь ничего конкретного) и вернуть значение, указывающее, действительна ли подпись или нет.

Если isValidSignature возвращает действительный результат, это почти то же самое, как если бы контракт сказал: «Да, я одобряю эту подпись + сообщение!»

Интерфейс

Вот точный интерфейс в спецификации EIP-1271 (мы поговорим о параметре _hash ниже, но пока думайте о нем как о проверяемом сообщении):

Пример реализации EIP-1271: Safe

Контракты могут реализовывать isValidSignature множеством способов — спецификация мало что говорит о точной реализации.

Одним из примечательных контрактов, реализующих EIP-1271, является Safe (ранее Gnosis Safe).

В коде Safe isValidSignature реализована (opens in a new tab) таким образом, что подписи могут создаваться и проверяться двумя способами (opens in a new tab):

  1. Сообщения ончейн
    1. Создание: владелец Safe создает новую транзакцию Safe, чтобы «подписать» сообщение, передавая сообщение в качестве данных в транзакцию. Как только достаточное количество владельцев подпишет транзакцию для достижения порога мультисига, транзакция транслируется и выполняется. В транзакции есть функция Safe под названием (signMessage(bytes calldata _data)), которая добавляет сообщение в список «одобренных» сообщений.
    2. Проверка: вызовите isValidSignature в контракте Safe и передайте проверяемое сообщение в качестве параметра сообщения, а также пустое значение для параметра подписи (opens in a new tab) (т. е. 0x). Safe увидит, что параметр подписи пуст, и вместо криптографической проверки подписи он будет знать, что нужно просто проверить, находится ли сообщение в списке «одобренных» сообщений.
  2. Сообщения офчейн:
    1. Создание: владелец Safe создает сообщение офчейн, затем просит других владельцев Safe подписать сообщение индивидуально, пока не наберется достаточно подписей для преодоления порога одобрения мультисига.
    2. Проверка: вызовите isValidSignature. В параметре сообщения передайте проверяемое сообщение. В параметре подписи передайте индивидуальные подписи каждого владельца Safe, объединенные вместе, одна за другой. Safe проверит, достаточно ли подписей для достижения порога, и действительна ли каждая подпись. Если это так, он вернет значение, указывающее на успешную проверку подписи.

Что именно представляет собой параметр _hash? Почему бы не передать все сообщение целиком?

Вы могли заметить, что функция isValidSignature в интерфейсе EIP-1271 (opens in a new tab) принимает не само сообщение, а параметр _hash. Это означает, что вместо передачи полного сообщения произвольной длины в isValidSignature, мы передаем 32-байтовый хеш сообщения (обычно keccak256).

Каждый байт данных вызова — т. е. данных параметров функции, передаваемых в функцию смарт-контракта, — стоит 16 газа (4 газа, если байт нулевой) (opens in a new tab), поэтому это может сэкономить много газа, если сообщение длинное.

Предыдущие спецификации EIP-1271

На практике встречаются спецификации EIP-1271, которые имеют функцию isValidSignature с первым параметром типа bytes (произвольной длины вместо фиксированной длины bytes32) и именем параметра message. Это более старая версия (opens in a new tab) стандарта EIP-1271.

Как следует реализовать EIP-1271 в моих собственных контрактах?

Спецификация здесь очень открытая. В реализации Safe есть несколько хороших идей:

  • Вы можете считать подписи EOA от «владельца» контракта действительными.
  • Вы можете хранить список одобренных сообщений и считать действительными только их.

В конечном итоге, решение остается за вами как за разработчиком контракта!

Заключение

EIP-1271 (opens in a new tab) — это универсальный стандарт, который позволяет смарт-контрактам проверять подписи. Он открывает двери для того, чтобы смарт-контракты действовали больше как EOA — например, предоставляя возможность функции «Войти с помощью Эфириума» работать со смарт-контрактами — и он может быть реализован множеством способов (Safe имеет нетривиальную, интересную реализацию для рассмотрения).