Pular para o conteúdo
ecdsa.com
Node.js · WebCrypto

viem

O viem, da wevm, a interface TypeScript para Ethereum usada pelo wagmi: assinaturas r‖s‖v de 65 bytes com v = 27/28, hash EIP-191 e RFC 6979 — mas, ao contrário do ethers, ele recupera assinaturas high-S e não lê as compactas EIP-2098.
Licença
MIT
Versão verificada
2.57.7 ·
Código-fonte
github.com/wevm/viem
Documentação
viem.sh/

Visão geral da compatibilidade

ECDSAEthereum e Bitcoin

Curvas ECDSA
secp256k1
EdDSA
none

O viem é uma biblioteca tipada e compatível com tree-shaking para clientes, contas, contratos e codificação ABI do Ethereum, e a base do wagmi. Contas locais criadas com privateKeyToAccount() assinam com @noble/curves (o viem 2.57.7 depende da versão 1.9.1 dela), então suas assinaturas são RFC 6979 determinísticas e low-S — idênticas, para a mesma chave e a mesma mensagem, às que o ethers produz.

account.signMessage() aplica o prefixo EIP-191 e o keccak-256 e retorna 65 bytes terminados em v = 27 ou 28; sign() com um parâmetro hash assina um digest de 32 bytes sem alterá-lo, que é como digests EIP-712 e hashes de transação são assinados. O teste confirmou os dois com implementações independentes.

A verificação é mais permissiva do que no ethers. recoverMessageAddress() e o utilitário verifyMessage aceitaram a assinatura high-S espelhada — matematicamente válida, mas uma forma que o ethers rejeita com non-canonical s. Se o seu sistema deduplica ou armazena assinaturas, normalize o s você mesmo, ou uma segunda assinatura, de aparência diferente, para a mesma mensagem será aceita. No sentido inverso, a forma compacta EIP-2098 de 64 bytes falhou com invalid signature length nas duas funções; expanda-a antes com parseCompactSignature e compactSignatureToSignature.

O utilitário avulso verifyMessage só verifica contas de propriedade externa. Carteiras de contrato inteligente (Safe, carteiras de abstração de contas, carteiras de chave de acesso) assinam via ERC-1271 ou ERC-6492, e só a ação verifyMessage de um cliente público consulta o contrato. Inspecione dados tipados com o inspetor de EIP-712, mensagens de login com o verificador de SIWE e assinaturas brutas com o verificador de assinaturas Ethereum.

O exemplo assina uma mensagem de login e a confere de duas formas: por verificação contra o endereço e pela recuperação do signatário.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/viem/run.sh executou viem 2.57.7 em Node.js 24.22.0; viem depends on @noble/curves 1.9.1 na imagem de contêiner node:24.22.0-bookworm-slim em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Codificação da assinatura ECDSA

    rsv-65

    signMessage() retorna 65 bytes em hex: r, s e v.

    Registrado pela verificação
    fact ecdsa-signature-encoding = rsv-65
  • ID de recuperação v

    27-28

    v é 27 ou 28; parseSignature() também o informa como yParity 0 ou 1.

    Registrado pela verificação
    fact recovery-v = 27-28
  • Prefixo da mensagem

    eip-191

    hashMessage() é igual ao keccak-256 do prefixo EIP-191, do comprimento em decimal e da mensagem, calculado de forma independente.

    Registrado pela verificação
    fact message-prefix = eip-191
  • Hash padrão

    keccak256

    As assinaturas de mensagens usam keccak-256.

    Registrado pela verificação
    fact ecdsa-default-hash = keccak256
  • Entrada do hash na chamada de assinatura

    hashes-message-or-digest

    signMessage() calcula o hash com o prefixo EIP-191; sign() com um parâmetro hash assina um digest de 32 bytes sem alterá-lo e produziu a mesma assinatura para esse digest.

    Registrado pela verificação
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: sign() over the EIP-191 digest equals signMessage(): true
  • Nonce do ECDSA

    rfc6979

    RFC 6979: a assinatura coincidiu com a assinatura determinística do @noble/curves sobre o mesmo digest.

    Registrado pela verificação
    fact ecdsa-nonce = rfc6979
  • Low-S ao assinar

    low-s

    Nenhum valor high-S em 64 assinaturas de mensagens.

    Registrado pela verificação
    fact ecdsa-low-s-sign = low-s
  • High-S ao verificar

    accepts-high-s

    A assinatura espelhada (r, n − s) com o v invertido recuperou o signatário em recoverMessageAddress() e foi verificada no utilitário verifyMessage.

    Registrado pela verificação
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: recoverMessageAddress with (r, n - s, flipped v): recovers the signer
  • Formatos de assinatura aceitos

    v-27-28v-0-1

    Assinaturas de 65 bytes com v = 27/28 ou v = 0/1 permitiram recuperar o signatário; a forma EIP-2098 de 64 bytes falhou com invalid signature length.

    Registrado pela verificação
    fact signature-input-shapes = v-27-28, v-0-1
    evidence signature-input-shapes: v-27-28: recovers the signer
    evidence signature-input-shapes: v-0-1: recovers the signer
    evidence signature-input-shapes: eip-2098: invalid signature length
    evidence signature-input-shapes: verifyMessage utility, high-S twin: true
    evidence signature-input-shapes: verifyMessage utility, eip-2098: invalid signature length
    evidence signature-input-shapes: eip-2098 expanded with parseCompactSignature and compactSignatureToSignature: recovers the signer
    evidence signature-input-shapes: parseSignature returns v 28 and yParity 1
  • Curvas ECDSA

    secp256k1

    Apenas secp256k1.

    Registrado pela verificação
    fact ecdsa-curves = secp256k1
  • EdDSA

    none

    Sem EdDSA.

    Registrado pela verificação
    fact eddsa = none
  • Algoritmos pós-quânticos

    none

    Sem algoritmos pós-quânticos.

    Registrado pela verificação
    fact pqc = none
  • Formatos de chave pública

    sec1-uncompressed

    account.publicKey é o ponto não comprimido SEC1 de 65 bytes.

    Registrado pela verificação
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: account.publicKey is 65 bytes starting with 0x04: true

Uso

Assine uma mensagem de login com uma conta local e depois verifique-a contra o endereço e recupere o signatário.
import { recoverMessageAddress, verifyMessage } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(generatePrivateKey());
const message = "Sign in to example.com\nNonce: 8f2c1a";

// EIP-191 personal_sign, RFC 6979, low-S, v = 27 or 28.
const signature = await account.signMessage({ message });

// For EOAs this is ecrecover plus an address comparison; for smart-contract
// wallets use the publicClient.verifyMessage action, which also checks ERC-1271.
const valid = await verifyMessage({ address: account.address, message, signature });
const signer = await recoverMessageAddress({ message, signature });

console.log("signature bytes:", (signature.length - 2) / 2);
console.log("valid:", valid, "signer matches:", signer === account.address);

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner node:24.22.0-bookworm-slim, e a execução falha se a saída não corresponder.

Configuração
npm init -y >/dev/null && npm install --no-audit --no-fund viem@2.57.7
Comando
node usage.mts
A saída deve corresponder a (expressão regular)
signature bytes: 65\nvalid: true signer matches: true

Bibliotecas