O ethers cobre tudo de que uma aplicação precisa para se comunicar com o Ethereum — provedores, contratos, codificação ABI, ENS —, e suas classes Wallet e SigningKey são o que a maior parte do código JavaScript usa para assinar. A aritmética da curva vem da @noble/curves; o ethers 6.17.0 fixa a versão 1.2.0 dela.
wallet.signMessage() implementa personal_sign: adiciona à mensagem o prefixo \x19Ethereum Signed Message:\n e o comprimento dela (EIP-191), calcula o hash com keccak-256 e retorna 65 bytes de r ‖ s ‖ v com v = 27 ou 28. O teste conferiu o digest com uma implementação independente de keccak e a assinatura com a saída RFC 6979 da @noble/curves, de modo que a mesma chave e a mesma mensagem sempre geram os mesmos bytes. Para assinar um hash que você já calculou — um digest EIP-712, o hash de uma transação —, use SigningKey.sign(), que recebe exatamente 32 bytes e não adiciona prefixo; qualquer outra entrada falha com invalid digest length.
Na entrada, o ethers é tolerante quanto ao formato e rigoroso quanto à forma. verifyMessage() recuperou o signatário a partir de v = 27/28, de v = 0/1 e da forma compacta EIP-2098 de 64 bytes, mas recusou a assinatura high-S espelhada com non-canonical s; use ._s — embora ela seja matematicamente válida. Assinaturas produzidas pelo OpenSSL, por KMS de nuvem ou por módulos de hardware que não normalizam o s precisam ser convertidas para low-S (s → n − s, com o v invertido) antes que o ethers as aceite; o verificador de assinaturas Ethereum mostra em que forma uma assinatura está.
A maioria dos bugs de verificação em logins com carteira nem tem a ver com a curva: uma mensagem que difere por uma quebra de linha ou por um espaço no final, um ID da cadeia diferente no domínio EIP-712 ou uma carteira de contrato inteligente cujas assinaturas exigem ERC-1271 em vez de ecrecover. Inspecione dados tipados com o inspetor de EIP-712 e mensagens de login completas com o verificador de SIWE.
O exemplo assina uma mensagem de login, recupera o endereço e mostra a forma compacta; compare os endereços recuperados, nunca os bytes da assinatura, porque a mesma assinatura tem várias codificações válidas.