Перейти к содержимому
Все статьи
Блокчейн

Подписи EIP-712 и permit: как защитить контракт от повтора подписи

·30 октября 2026 г.· 11 мин чтения
Нужен смарт-контракт или dApp?Разработка смарт-контрактов

Подпись EIP-712 — это подпись структурированных данных, которую пользователь делает в кошельке без отправки транзакции, а контракт потом проверяет. На ней построен permit (EIP-2612): разрешение на списание токенов выдаётся подписью вместо отдельной транзакции approve. Главный риск — повтор подписи: одну и ту же подпись используют дважды, в другой сети или в другом контракте. Защита обязательна и состоит из пяти элементов: nonce, срок действия, домен с chainId и адресом контракта, проверка подписанта и корректная обработка ошибок.

Как устроена подпись EIP-712

EIP-712 задаёт, как превратить структуру данных в хеш, который кошелёк покажет пользователю в читаемом виде. Хеш собирается из двух частей. Домен описывает, где подпись действительна: название и версия приложения, chainId сети и адрес проверяющего контракта. Сообщение — сами данные: кому, сколько, до какого времени, с каким номером.

Поле доменаОт чего защищает
name, versionОт повтора в другом приложении или старой версии контракта
chainIdОт повтора в другой сети, где развёрнут такой же контракт
verifyingContractОт повтора в другом контракте той же сети
salt (необязательно)Дополнительное разделение, если первых полей недостаточно

В OpenZeppelin за домен отвечает контракт EIP712. Он вычисляет разделитель домена при деплое и пересчитывает его, если chainId изменился, например после разделения сети. С версии 5.7 название и версия в EIP712 должны укладываться в 31 байт, иначе конструктор откатится, — учитывайте это при выборе длинного названия токена.

Permit: approve без транзакции

Обычный сценарий работы с токеном требует двух транзакций: approve, затем вызов контракта, который списывает токены. EIP-2612 добавляет функцию permit(owner, spender, value, deadline, v, r, s). Пользователь подписывает разрешение в кошельке, а контракт вызывает permit и списание в одной транзакции. В OpenZeppelin это расширение ERC20Permit, оно уже содержит nonce и проверку срока.

Пять защит от повтора

  1. Nonce. У каждого подписанта счётчик, который увеличивается при каждом использовании подписи. Использованная подпись больше не пройдёт. В OpenZeppelin это контракт Nonces и функция _useNonce(owner).
  2. Срок действия. Поле deadline в сообщении и проверка if (block.timestamp > deadline) revert Expired();. Подпись без срока живёт вечно.
  3. Полный домен. chainId и verifyingContract в домене, а не собственная «упрощённая» схема хеширования.
  4. Проверка подписанта. Восстановленный адрес сравнивается с ожидаемым, а нулевой адрес отклоняется.
  5. Все значимые поля в подписи. Получатель, сумма, токен, назначение. Всё, что не подписано, может подставить тот, кто отправляет транзакцию.

Учебный пример проверки

Учебный пример контракта, который исполняет заявку пользователя по подписи. Структура: Order(address owner,address to,uint256 amount,uint256 nonce,uint256 deadline).

  • bytes32 constant ORDER_TYPEHASH = keccak256("Order(address owner,address to,uint256 amount,uint256 nonce,uint256 deadline)");
  • if (block.timestamp > o.deadline) revert Expired();
  • bytes32 structHash = keccak256(abi.encode(ORDER_TYPEHASH, o.owner, o.to, o.amount, _useNonce(o.owner), o.deadline));
  • bytes32 digest = _hashTypedDataV4(structHash); — домен добавляется автоматически.
  • if (!SignatureChecker.isValidSignatureNow(o.owner, digest, signature)) revert BadSignature();

_useNonce возвращает текущий номер и сразу увеличивает его, поэтому вторая попытка с той же подписью не совпадёт с хешем. SignatureChecker проверяет и обычные подписи кошельков, и подписи смарт-кошельков по ERC-1271.

Тонкие места

Перехват permit

Подпись permit видна в мемпуле. Кто угодно может отправить её первым и вызвать permit напрямую. Средства он не получит, ведь разрешение выдаётся указанному spender, но nonce израсходуется, и транзакция пользователя упадёт. Документация OpenZeppelin рекомендует оборачивать вызов permit в try/catch и продолжать, если разрешение уже установлено.

Смарт-кошельки и EIP-7702

Всё больше пользователей работают через смарт-кошельки, а после Pectra (EIP-7702) у обычного адреса может появиться делегированный код. Проверка только через ECDSA.recover не примет подпись смарт-кошелька. Используйте SignatureChecker, если ваша аудитория может подписывать через контракт.

Подпись в нескольких сетях

Если один и тот же контракт развёрнут в нескольких сетях по одинаковому адресу, например через CREATE2, домен без chainId сделает подпись из одной сети действительной в другой. Это одна из причин не писать собственное хеширование, а использовать EIP712 из OpenZeppelin. Тест, который проверяет отказ подписи после смены chainId через vm.chainId(...) в Foundry, стоит включить в набор обязательных.

Подписи, которые пользователь не понимает

Подпись permit с неограниченной суммой и далёким сроком — частый инструмент фишинга: пользователь подписывает «вход на сайт», а на деле выдаёт разрешение на все токены. Делайте интерфейс честным: показывайте сумму, получателя и срок, запрашивайте ровно необходимое, а срок ставьте коротким.

Где ещё используют подписи

СценарийЧто подписывает пользователь или серверГлавный риск
Вайтлист NFTСервер подписывает адрес и лимит минтаПодпись без лимита и срока, утечка ключа сервера
Раздача токеновСервер подписывает получателя и суммуПовтор подписи, если нет учёта уже выданного
Мета-транзакцииПользователь подписывает вызов, газ платит ретрансляторПодмена параметров, которых нет в подписи
Ордера на обменПользователь подписывает условия сделкиИсполнение по устаревшим условиям без срока
Голосование по подписиГолос за предложениеПовтор голоса в другом предложении

Когда подписывает сервер, а не пользователь, ключ сервера становится таким же ценным, как ключ администратора контракта. Его хранят отдельно от приложения, в защищённом хранилище, с журналом использования и возможностью быстро заменить. Если ключ утёк, контракт должен позволять сменить адрес доверенного подписанта, а старые подписи — перестать работать. Общие правила обращения с ключами и журналами мы разбирали в статье о токенах и паролях в журналах приложения.

Таблица проверки

ВопросЧто должно быть
Есть ли nonce и расходуется ли он до внешних вызовов?Да, _useNonce или эквивалент
Есть ли срок действия?Да, проверка deadline
Входят ли chainId и адрес контракта в домен?Да, через EIP712 из OpenZeppelin
Подписаны ли все поля, которые влияют на результат?Получатель, сумма, токен, назначение
Как проверяется подпись?ECDSA или SignatureChecker, не голый ecrecover
Что будет при перехвате permit?try/catch и проверка текущего разрешения
Можно ли отозвать подпись?Функция, которая увеличивает nonce владельца

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

Вопросы и ответы

Что такое EIP-712?

Это стандарт подписи структурированных данных в Ethereum и совместимых сетях. Кошелёк показывает пользователю понятные поля, а контракт проверяет подпись с учётом сети, адреса контракта и версии приложения.

Что такое permit в ERC-20?

Это функция из EIP-2612, которая выдаёт разрешение на списание токенов по подписи владельца, без отдельной транзакции approve. Это экономит одну транзакцию и газ.

Как защититься от повтора подписи?

Использовать nonce, срок действия, домен с chainId и адресом контракта, проверять подписанта через библиотеку ECDSA или SignatureChecker и подписывать все значимые поля.

Можно ли отменить выданную подпись?

Только если контракт это предусматривает: обычно через функцию, которая увеличивает nonce владельца. Тогда все ещё не использованные подписи с прежним номером перестают работать.

Опасно ли подписывать сообщения в кошельке?

Да, если не понимаете, что подписываете. Подпись permit может дать разрешение на списание токенов. Проверяйте сумму, получателя, срок и адрес сайта.

Нужна помощь с проектом?

Обсудим задачу и предложим решение — от сайта до SaaS и защиты.

Связаться