ethers умеет всё, что нужно приложению для работы с Ethereum (провайдеры, контракты, кодирование ABI, ENS), а большая часть кода на JavaScript подписывает данные классами Wallet и SigningKey из ethers. Арифметику на кривой библиотека берёт из @noble/curves; ethers 6.17.0 жёстко фиксирует её версию 1.2.0.
wallet.signMessage() реализует personal_sign: добавляет перед сообщением \x19Ethereum Signed Message:\n и его длину (EIP-191), хеширует результат keccak-256 и возвращает 65 байт r ‖ s ‖ v с v = 27 или 28. Проверка сверила дайджест с независимой реализацией keccak, а подпись — с результатом RFC 6979 из @noble/curves, поэтому одинаковые ключ и сообщение всегда дают одинаковые байты. Чтобы подписать уже вычисленный хеш (дайджест EIP-712, хеш транзакции), используйте SigningKey.sign(): этот метод принимает ровно 32 байта и не добавляет префикс, а на любые другие данные отвечает ошибкой invalid digest length.
Библиотека терпима к структуре входных данных, но строга к их форме. verifyMessage() восстановил подписанта по подписям с v = 27/28 и с v = 0/1, а также по 64-байтовой компактной форме EIP-2098, но отклонил зеркальную подпись с high-S с ошибкой non-canonical s; use ._s, хотя математически она верна. Подписи от OpenSSL, облачных KMS или аппаратных модулей, которые не нормализуют s, нужно привести к low-S (s → n − s и смена v), прежде чем ethers их примет; проверка подписей Ethereum показывает, в какой форме записана подпись.
Большинство ошибок проверки при входе через кошелёк вообще не связаны с кривой: сообщение, которое отличается переводом строки или пробелом в конце; другой chain ID в домене EIP-712; кошелёк на смарт-контракте, подписи которого нужно проверять через ERC-1271, а не ecrecover. Типизированные данные разбирайте в инспекторе EIP-712, а полные сообщения для входа — в средстве проверки SIWE.
Пример подписывает сообщение для входа, восстанавливает адрес и показывает компактную форму. Сравнивайте восстановленные адреса, но никогда не байты подписей: у одной и той же подписи есть несколько допустимых кодировок.