ERC-1271: проверка подписи смарт-аккаунта
Предыдущий подписной реестр проверял ECDSA и поэтому имел ограничение: издатель представлен ключом, подходящим этой схеме. Организационный кошелёк может проверять разрешение иначе — по нескольким участникам, собственной политике или текущему состоянию. Для такого подписанта существует ERC-1271.
В этой главе добавим контрактную проверку к отдельному подписному примеру. Это не новая реализация полного кошелька и не интеграция реальной организации. Используем Solidity 0.8.30 и OpenZeppelin 5.4.0 как профиль чтения исходников; подписи, контракты и проверки сейчас не выполняются.
Проверяет сам контракт подписанта
В ERC-1271 предусмотрен вызов isValidSignature(hash, signature). Успех выражается определённым magic value, а не любым truthy-ответом. Проверка не должна изменять состояние, поэтому вызывающий использует соответствующий статический контекст.
Важно, что valid означает решение кошелька на момент проверки. Его владельцы, разрешённые подписанты или другие условия могут измениться. Сохранённый вчера положительный результат не является вечным разрешением на новое действие сегодня.
Nonce остаётся обязанностью нашего приложения. Даже если контрактный кошелёк признал подпись, одно и то же разрешение не должно повторно менять реестр после потребления nonce. Политика кошелька и однократность публикации решают разные задачи.
Используем закреплённую проверку библиотеки
В собственном SignedCourseCatalog из прошлого урока заменяем импорт ECDSA на SignatureChecker. Затем вместо восстановления одного ключа вызываем библиотечную проверку относительно immutable издателя. Остальные поля, домен, nonce, ревизия и срок сохраняются.
import {SignatureChecker} from
"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
// В publishSigned, после вычисления того же EIP-712 digest:
if (!SignatureChecker.isValidSignatureNow(publisher, digest, signature)) {
revert Unauthorized();
}
Это точечное изменение отдельного исходника, а не самостоятельный полный контракт. В будущей лаборатории его сохранят новым вариантом и сверят весь API. Базовый CourseCatalog при этом не приобретает подписной маршрут скрыто.
В исходнике 5.4.0 видно, как выбирается обычная ECDSA-проверка или статический вызов ERC-1271, а также проверяется форма возвращённых данных. Импорт без анализа закреплённой версии не сообщает этих условий автоматически.
Создаём собственную управляемую фикстуру
Чтобы объяснить отзыв разрешения, напишем маленькую модель Lab1271Signer. У неё один immutable управляющий адрес и переключатель допустимости. Она не выполняет произвольные транзакции и не объявляется полноценным multisig-кошельком.
pragma solidity 0.8.30;
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract Lab1271Signer {
address public immutable owner;
bool public enabled = true;
error Unauthorized();
constructor(address owner_) {
require(owner_ != address(0), "owner required");
owner = owner_;
}
function setEnabled(bool value) external {
if (msg.sender != owner) revert Unauthorized();
enabled = value;
}
function isValidSignature(bytes32 digest, bytes calldata signature)
external view returns (bytes4)
{
if (!enabled) return 0xffffffff;
(address recovered, ECDSA.RecoverError err,) = ECDSA.tryRecover(digest, signature);
if (err != ECDSA.RecoverError.NoError || recovered != owner) return 0xffffffff;
return 0x1626ba7e;
}
}
Ожидаемый сценарий: управляющий ключ подписал digest, переключатель включён, кошелёк признаёт разрешение. После setEnabled(false) те же байты больше не проходят. Это разбор ветвей листинга, а не реально созданная подпись или выполненный вызов.
Контракт подписанта — адрес, который назначается publisher в новом варианте реестра. Управляющий ключ фикстуры — другой субъект, используемый внутри проверки. Если сравнить recovered с адресом самой фикстуры как будто это её приватный ключ, модель будет неправильной.
Проверяем в момент исполнения
Предварительная симуляция или чтение полезны интерфейсу, но не заменяют on-chain проверку при применении. Между подготовкой и исполнением политика кошелька может измениться. Тогда разрешение отклоняется, а nonce нашего реестра остаётся прежним при полном revert.
Если проверка прошла, следующий шаг потребляет nonce и изменяет курс. Успешная публикация не отменяется задним числом только потому, что кошелёк позже перестал признавать прежнюю подпись. Для изменения уже опубликованного состояния нужно отдельное разрешённое действие.
Не кешируем результат valid=true на сервере как долгоживущий bearer-token. Серверная сессия, подпись действия и право изменения контракта являются разными объектами. Курс идентификации подробнее показывает, где допустима собственная сессия и как она связана с исходной проверкой.
Не считаем размер кода доказательством личности
Библиотека использует наличие кода для выбора конкретной процедуры. Это техническое решение закреплённой версии, а не универсальное определение всех видов аккаунтов. Современные механизмы делегированного кода требуют отдельной совместимости с кошельком и сетью.
В нашем примере предполагаем, что адрес с контрактной проверкой реализует нужную семантику ERC-1271. Если это не так, не переключаемся произвольно на другой алгоритм ради успешного результата. Отказ означает неподдерживаемый либо недействительный путь, который нужно объяснить пользователю.
Также контракт подписанта может вернуть ошибку или слишком короткие данные. Библиотечная проверка не должна считать это разрешением. При недоступности нужного условия операции отказывают, сохраняя явную границу доверия.
Меняем условие: чужой кошелёк
Участник передал ту же подпись и предложил другой адрес signer в query. Реестр продолжает проверять immutable publisher. Подпись не становится допустимой лишь потому, что какой-то чужой контракт согласился её признать.
Если организация хочет сменить издателя, потребуется явно введённая административная процедура в другой модели. Наш листинг такой возможности не содержит. UI не может заменить адрес издателя после подготовки разрешения и сохранить прежние гарантии домена.
Что хранится после проверки
После успешного применения сохраняется не общий флаг «этот кошелёк доверенный», а конкретный переход курса и потреблённый nonce. Следующее разрешение проверяется заново. Это позволяет организационному кошельку менять собственную политику, не превращая одно положительное чтение в бесконечную привилегию.
Для объяснения отказа достаточно записать домен, digest, адрес издателя и момент наблюдения. Секрет управляющего ключа не требуется и не должен попадать в служебный журнал. Точные байты подписи рассматриваются по отдельной политике хранения, поскольку их повторная доставка может иметь значение до потребления разрешения.
Контрактная проверка также расходует gas и может зависеть от внешних чтений подписанта. Поэтому поддержка ERC-1271 не обещает одинаковую стоимость всех кошельков. В будущей эксплуатации отдельно оценивают доступность процедуры и пределы вызова, не ослабляя проверку до любого возвращённого значения.
Разбор результата
Контрактная подпись теперь проверяется по текущей политике заранее назначенного кошелька. ERC-1271 не заменяет nonce, домен или авторизацию, а предварительный ответ не обещает вечной действительности. Управляемая фикстура показывает изменение решения без выдуманного полноценного кошелька.
В следующем уроке рассмотрим отказ обслуживания и границу общего ресурса. Полное оглавление связывает оставшиеся темы курса.