EIP-1344: CHAINID — защита от повтора для подписей контрактов
EIP-155 привязал транзакции к их сети. Но контракты всё чаще верифицируют собственные подписанные сообщения — безгазовые одобрения, мета-транзакции, офчейн-ордера — а у них не было никакой привязки к сети, поэтому подпись, действительная для контракта в одной сети, повторно проходила в другой. CHAINID открывает EVM доступ к id текущей сети, так что контракт может привязать свои подписи к сети, на которой он реально работает.
- защита от повтора EIP-155
- подписи и ecrecover
Стамбул, декабрь 2019 года. EIP-155 решил проблему межсетевого повтора для транзакций, встроив chainId в то, что подписывается. Но к 2019 году внутри контрактов вырос целый мир подписей — безгазовые одобрения токенов (permit), мета-транзакции, офчейн-ордера DEX — где пользователь подписывает сообщение, а контракт верифицирует его через ecrecover. У этих подписей была ровно та же проблема, что решил 155, только уровнем выше: никакой привязки к сети, поэтому подпись, действительная для контракта в одной сети, была действительна для того же контракта везде. Загвоздка в том, что контракт не может просто знать свою сеть — если только протокол ему не скажет. EIP-1344 — это опкод, который ему это говорит. Давайте выведем его.
Проблема: подписи контракта повторяются где угодно
Контракт, принимающий подписанные сообщения, верифицирует их через ecrecover Прекомпайл EVM, восстанавливающий адрес подписавшего по хешу сообщения и подписи (v, r, s). Контракт сравнивает восстановленный адрес с ожидаемым — на этом строятся permit, мета-транзакции и офчейн-ордера. : восстанавливаем подписавшего из (hash, v, r, s) и проверяем, что это тот, кто нужен. Но если хеш, который подписал пользователь, ничего не говорит о том, в какой сети работает контракт, то одна и та же подпись восстанавливает того же подписавшего для того же адреса контракта в любой сети — а адреса контрактов часто совпадают между сетями (особенно после форка или через CREATE2). Так что permit, подписанный вами в mainnet, можно повторно провести против контракта-близнеца в другой сети.
→ Шаг 2: дадим EVM способ читать сеть.
CHAINID: id текущей сети, во время выполнения
Зашить id на момент деплоя заманчиво, но неверно — почему, увидим через минуту, — так что настоящее решение делает id сети читаемым во время работы контракта. CHAINID Опкод 0x46 из Istanbul. Кладёт в стек id сети, в которой исполняется транзакция, — то же значение, что использует EIP-155, — так что код контракта может читать свою сеть во время выполнения. (опкод 0x46) кладёт id текущей сети в стек. Теперь контракт может встроить этот id в разделитель домена Значение EIP-712, ограничивающее подпись конкретным приложением и деплоем: хеширует вместе имя контракта, версию, chainId и адрес верифицирующего контракта. Подписи делаются над (разделителем домена, сообщением), поэтому действительны только в этом точном домене. — значение EIP-712, ограничивающее подпись этим приложением, этим контрактом, этой сетью. Сообщение, которое подписывает пользователь, составляется поверх этого домена, поэтому подпись действительна только там, где домен совпадает.
- 1 читается во время выполнения (0x46) — поэтому отражает сеть, в которой контракт реально работает
- 2 ограничивает именно этим контрактом, а не только этой сетью
→ Шаг 3: расщепление — вот в чём весь смысл.
Почему это нужно читать «вживую»: расщепление сети
Представьте хардфорк, расщепляющий одну сеть на две — ровно тот сценарий, из которого родился EIP-155. Ваш контракт теперь существует, байт в байт, на обеих сетях по одному и тому же адресу. Если бы он зашил id сети на момент деплоя, обе копии несли бы одну и ту же константу, и подпись по-прежнему проходила бы на обеих — дыра никогда не закроется. Поскольку CHAINID читается во время выполнения, две копии теперь возвращают разные id: в исходной сети это 1, в новой — скажем, 61. Разделитель домена отличается, поэтому подпись, сделанная для сети 1, при пересчёте даёт домен, не совпадающий с сетью 61, и отклоняется. Защита автоматически отслеживает расщепление.