viem — типизированная библиотека с поддержкой tree shaking для клиентов Ethereum, аккаунтов, контрактов и кодирования ABI, на которой построен wagmi. Локальные аккаунты из privateKeyToAccount() подписывают с помощью @noble/curves (viem 2.57.7 зависит от её версии 1.9.1), поэтому их подписи детерминированы по RFC 6979 и имеют low-S, то есть для того же ключа и сообщения совпадают с тем, что выдаёт ethers.
account.signMessage() добавляет префикс EIP-191, хеширует keccak-256 и возвращает 65 байт, заканчивающиеся значением v = 27 или 28; sign() с параметром hash подписывает 32-байтовый дайджест без изменений, и именно так подписывают дайджесты EIP-712 и хеши транзакций. Проверка подтвердила оба результата по независимым реализациям.
Проверка здесь мягче, чем в ethers. recoverMessageAddress() и вспомогательная функция verifyMessage приняли зеркальную подпись с high-S: математически она верна, но ethers отклоняет такую форму с ошибкой non-canonical s. Если ваша система устраняет дубликаты подписей или хранит их, нормализуйте s сами, иначе будет принята вторая, внешне отличающаяся подпись того же сообщения. С другой стороны, 64-байтовую компактную форму EIP-2098 обе функции отклонили с ошибкой invalid signature length; сначала разверните её с помощью parseCompactSignature и compactSignatureToSignature.
Отдельная вспомогательная функция verifyMessage проверяет только внешние аккаунты (EOA). Кошельки на смарт-контрактах (Safe, кошельки с абстракцией аккаунтов, кошельки на ключах доступа) подписывают через ERC-1271 или ERC-6492, и к контракту обращается только действие verifyMessage публичного клиента. Разбирайте типизированные данные в инспекторе EIP-712, сообщения для входа — в средстве проверки SIWE, а необработанные подписи — в проверке подписей Ethereum.
Пример подписывает сообщение для входа и проверяет его двумя способами: проверкой по адресу и восстановлением подписанта.