Часть III · Глава 17 из 43 · Istanbul

EIP-1344: CHAINID — защита от повтора для подписей контрактов

EIP-155 привязал транзакции к их сети. Но контракты всё чаще верифицируют собственные подписанные сообщения — безгазовые одобрения, мета-транзакции, офчейн-ордера — а у них не было никакой привязки к сети, поэтому подпись, действительная для контракта в одной сети, повторно проходила в другой. CHAINID открывает EVM доступ к id текущей сети, так что контракт может привязать свои подписи к сети, на которой он реально работает.

Обновлено 24 июн. 2026 г. · 9 мин
Предполагается
  • защита от повтора EIP-155
  • подписи и ecrecover

Стамбул, декабрь 2019 года. EIP-155 решил проблему межсетевого повтора для транзакций, встроив chainId в то, что подписывается. Но к 2019 году внутри контрактов вырос целый мир подписей — безгазовые одобрения токенов (permit), мета-транзакции, офчейн-ордера DEX — где пользователь подписывает сообщение, а контракт верифицирует его через ecrecover. У этих подписей была ровно та же проблема, что решил 155, только уровнем выше: никакой привязки к сети, поэтому подпись, действительная для контракта в одной сети, была действительна для того же контракта везде. Загвоздка в том, что контракт не может просто знать свою сеть — если только протокол ему не скажет. EIP-1344 — это опкод, который ему это говорит. Давайте выведем его.

1 шаг Подписи без привязки к сети

Проблема: подписи контракта повторяются где угодно

Контракт, принимающий подписанные сообщения, верифицирует их через ecrecover Прекомпайл EVM, восстанавливающий адрес подписавшего по хешу сообщения и подписи (v, r, s). Контракт сравнивает восстановленный адрес с ожидаемым — на этом строятся permit, мета-транзакции и офчейн-ордера. : восстанавливаем подписавшего из (hash, v, r, s) и проверяем, что это тот, кто нужен. Но если хеш, который подписал пользователь, ничего не говорит о том, в какой сети работает контракт, то одна и та же подпись восстанавливает того же подписавшего для того же адреса контракта в любой сети — а адреса контрактов часто совпадают между сетями (особенно после форка или через CREATE2). Так что permit, подписанный вами в mainnet, можно повторно провести против контракта-близнеца в другой сети.

контракт проверяет подписанное сообщение — но в какой цепи? подписанный permit ecrecover(v,r,s) Цепь A · 0xTkn ✓ одобрено Цепь B · 0xTkn ✓ повтор прошёл! одна подпись работает на всех цепях с тем же адресом контракта
Пользователь подписывает permit; контракт верифицирует его через ecrecover. Поскольку подписанное сообщение не несёт привязки к сети, идентичная подпись принимается для того же адреса контракта в любой сети — межсетевой повтор, та же проблема, что решал EIP-155, только на уровне подписей контракта.

→ Шаг 2: дадим EVM способ читать сеть.

2 шаг Читаем сеть во время выполнения

CHAINID: id текущей сети, во время выполнения

Зашить id на момент деплоя заманчиво, но неверно — почему, увидим через минуту, — так что настоящее решение делает id сети читаемым во время работы контракта. CHAINID Опкод 0x46 из Istanbul. Кладёт в стек id сети, в которой исполняется транзакция, — то же значение, что использует EIP-155, — так что код контракта может читать свою сеть во время выполнения. (опкод 0x46) кладёт id текущей сети в стек. Теперь контракт может встроить этот id в разделитель домена Значение EIP-712, ограничивающее подпись конкретным приложением и деплоем: хеширует вместе имя контракта, версию, chainId и адрес верифицирующего контракта. Подписи делаются над (разделителем домена, сообщением), поэтому действительны только в этом точном домене. — значение EIP-712, ограничивающее подпись этим приложением, этим контрактом, этой сетью. Сообщение, которое подписывает пользователь, составляется поверх этого домена, поэтому подпись действительна только там, где домен совпадает.

привязать подпись к цепи: читать CHAINID во время выполнения CHAINID 0x46 → 1 domain separator chainId: 1 contract: 0xTkn сообщение подписано в рамках этого домена теперь подпись верна только для chain 1 и только для этого контракта
CHAINID (0x46) возвращает id сети, в которой выполняется код. Контракт встраивает его в свой разделитель домена EIP-712 (chainId + верифицирующий контракт), а сообщение пользователя подписывается поверх этого домена — так что подпись действительна только в сети 1 и только для этого контракта.
Формула
domainSeparator = keccak256( name, version, CHAINID 1 , address(this) 2 )
  1. 1 читается во время выполнения (0x46) — поэтому отражает сеть, в которой контракт реально работает
  2. 2 ограничивает именно этим контрактом, а не только этой сетью
Подпишите поверх этого домена — и подпись намертво сварена с (этой сетью, этим контрактом) — версия трюка EIP-155 на уровне контракта.

→ Шаг 3: расщепление — вот в чём весь смысл.

3 шаг Переживаем расщепление сети

Почему это нужно читать «вживую»: расщепление сети

Представьте хардфорк, расщепляющий одну сеть на две — ровно тот сценарий, из которого родился EIP-155. Ваш контракт теперь существует, байт в байт, на обеих сетях по одному и тому же адресу. Если бы он зашил id сети на момент деплоя, обе копии несли бы одну и ту же константу, и подпись по-прежнему проходила бы на обеих — дыра никогда не закроется. Поскольку CHAINID читается во время выполнения, две копии теперь возвращают разные id: в исходной сети это 1, в новой — скажем, 61. Разделитель домена отличается, поэтому подпись, сделанная для сети 1, при пересчёте даёт домен, не совпадающий с сетью 61, и отклоняется. Защита автоматически отслеживает расщепление.

при разделении CHAINID разный — подпись не переносится the same sig Цепь A · CHAINID 1 domain uses 1 ✓ совпадает Цепь B · CHAINID 61 domain uses 61 ✗ отклонено пересчитывать домен при смене CHAINID — иначе кеш открывает дыру
После расщепления один и тот же контракт работает в обеих сетях, но CHAINID возвращает в каждой разный id. Подпись, сделанная для сети 1, совпадает с доменом там (✓), но не в сети 61, где домен теперь использует 61 (✗). Оговорка: контракт, закешировавший разделитель домена при деплое, должен пересчитать его при изменении CHAINID — иначе кеш заново откроет дыру.
04 Глубже Куда двигаться дальше