Перейти к содержанию

Web3: проверка подписи обычного аккаунта

Оглавление курса «Web3: вход, смарт-аккаунты и индексатор» · Урок 5 из 30.

После сопоставления challenge остаётся доказать, что сообщение подписано подходящим аккаунтом. Для обычного EOA это проверка Ethereum-подписи сообщения. В этом уроке рассмотрим её место в серверном маршруте и не будем превращать успешный криптографический результат в сессию до одноразового потребления nonce. Результат — все условия входа соединены в правильном порядке.

Используем Node22.14.0 и viem2.37.9, фиксированное сообщение SIWE и серверный Public Client локальной сети. Настоящие подписи, сервер и тесты не запускались. Показанный код является подготовленным исходником логического обработчика, а не готовым HTTP-приложением с cookie и базой данных.

Подпись относится к исходному сообщению

EOA-подпись проверяется по правилам подписания сообщения Ethereum, а не по произвольному SHA256 текста. SIWE задаёт формат, а стандартный механизм подписи использует соответствующее назначение байтов. Самостоятельно собирать криптографический алгоритм из случайных хеш-функций здесь не нужно. Библиотека получает исходную строку и ожидаемый адрес.

Проверка правильности подписи не сообщает, что nonce ещё свободен. Она также не выбирает роль читателя в библиотеке. Это один шаг в цепочке серверных условий. Если подпись корректна, но challenge уже потреблён, новый вход отвергается. Если подпись корректна и сессия создана, конкретное защищённое действие всё равно проходит авторизацию.

Для общего маршрута viem предлагает verifySiweMessage, который проверяет сообщение и поддерживает серверный клиентский контекст. В уроке о контрактных аккаунтах разберём, почему сетевой источник становится значимым. Для чистого EOA восстановление адреса можно рассматривать отдельно, однако весь сайт не должен молча ограничивать тип аккаунта без явного договора.

Подготовленный обработчик

Файл verify-login.js принимает существующее хранилище challenge и функцию создания сессии. checkChallengeFields определён в предыдущем уроке, а serverClient — Public Client нужной сети. HTTP-параметры и cookie пока находятся вне функции.

export async function verifyLogin({ nonce, message, signature, flowId }, {
  store, serverClient, createSession,
}) {
  const challenge = store.get(nonce);
  if (!challenge || challenge.flowId !== flowId) {
    throw new Error('Нужна новая попытка входа');
  }
  const fields = checkChallengeFields(message, challenge);
  const valid = await serverClient.verifySiweMessage({
    message,
    signature,
    domain: 'library.example',
    nonce: challenge.nonce,
  });
  if (!valid) throw new Error('Подпись не подтверждена');
  if (!store.consume(challenge.nonce, flowId)) {
    throw new Error('Попытка уже использована или истекла');
  }
  return createSession({ address: fields.address, chainId: fields.chainId });
}

createSession не вызывается до успешного одноразового перехода. В учебном процессе он создаст обычную серверную запись с непредсказуемым идентификатором. Для нескольких процессов используется общая база и согласованная транзакция, как объяснялось в уроке nonce. Этот адаптер в памяти не обещает распределённую гарантию сам по себе.

Входные размеры и форматы подписи ограничиваются до вызова функции. Неподходящая шестнадцатеричная строка не должна бесконечно нагружать библиотеку или попадать в полный журнал. Ошибка формата, отрицательный результат и отказ сетевой проверки имеют разные диагностические категории, хотя человеку можно показывать короткое безопасное сообщение.

Подпись другого адреса

Браузер мог подписать challenge после смены аккаунта. Сообщение всё ещё содержит исходный адрес. Корректная подпись другого аккаунта не удовлетворяет этому challenge. Сервер не переписывает адрес внутри сообщения, чтобы «помочь» входу. Пользователь начинает новый маршрут для текущего выбора.

Если библиотека вернула валидность, это относится к точной строке и выбранному механизму проверки. Не следует дополнительно доверять адресу из другого поля JSON запроса. Окончательная сессия связывается с адресом, полученным из проверенного сообщения, а не с клиентским currentAccount или красивым именем на странице.

Для адреса издателя это всё равно только вход. Сервер может предоставить редакционные функции сайта по своей политике, но не приобретает возможность подписывать publish. Запись реестра требует отдельной пользовательской операции или заранее спроектированной серверной роли с собственным управлением ключом. Наш курс такого автоматического полномочия не вводит.

Ошибка источника проверки

Для EOA библиотека может проверить подпись без состояния контракта, но общий маршрут допускает контрактные аккаунты. Если сетевой источник недоступен, нельзя заменить проверку ответом «наверное обычный аккаунт» и принять сообщение по упрощённой ветке. Тип и выбранная политика имеют конкретные условия. Неизвестность остаётся отказом завершить вход, а не разрешением.

Даже когда подпись математически верна, сервер повторно проверяет срок при потреблении. Если человек долго читал сообщение в кошельке, ему может понадобиться новый challenge. Это ожидаемое следствие ограниченного срока, которое интерфейс объясняет спокойно. Автоматическая повторная подпись не запускается без действия человека.

Журнал аутентификации

Безопасная запись содержит результат этапа, время, идентификатор попытки и нужные поля происхождения. Полные подпись и сообщение с личными resources не записываются в общую аналитику. Технический журнал имеет ограниченный доступ и срок хранения; даже публичный адрес создаёт связь с посещением конкретного кабинета.

Ошибки не должны раскрывать, зарегистрирован ли чужой адрес или какой токен у него есть. На этапе входа сервер устанавливает участника, а персональные сведения доступны после сессии и авторизации. Общая фраза о невозможности завершить попытку может быть достаточной, если подробная причина принадлежит внутренней диагностике.

Ожидаемые случаи

Правильная подпись свежего challenge приводит к одной сессии. Неверная подпись не потребляет challenge в выбранной политике, но увеличивает счётчик ограничений. Правильная повторная подпись уже использованного сообщения не создаёт новую сессию. Истёкшая запись требует нового входа. Это заранее заданные критерии, не проведённые тесты.

Для упражнения разместите все проверки в последовательности и покажите, где возникает асинхронный промежуток. Затем объясните, почему атомарный consume нужен даже после успешной библиотеки. Ожидаемый итог — проверка подписи встроена в серверный процесс без лишних полномочий. API приведён в verifySiweMessage, формат — в ERC-4361.

Криптографический результат не является HTTP-ответом

Функция проверки возвращает решение внутри серверного процесса. Затем HTTP-адаптер создаёт cookie и ограниченный ответ кабинета. Если сеть оборвалась после создания сессии, браузер может не получить cookie. Повтор старого challenge всё равно не создаёт вторую сессию: одноразовая запись уже использована. Восстановление начинается новым понятным потоком, а не ослаблением consume.

При анализе ошибки сервер отделяет факт созданной записи от доставки ответа. Такой журнал помогает убрать потерянные сессии и объяснить повторный вход. Сами подпись и seed-фраза туда не записываются. Ограниченные диагностические сведения позволяют сопровождать процесс без доступа к секретам пользователя.

Оглавление серии