Saltar al contenido
ecdsa.com
Node.js · WebCrypto

viem

viem de wevm, la interfaz TypeScript para Ethereum en la que se basa wagmi: firmas r‖s‖v de 65 bytes con v = 27/28, hash EIP-191 y RFC 6979, pero, a diferencia de ethers, recupera firmas high-S y no lee las firmas compactas EIP-2098.
Licencia
MIT
Versión comprobada
2.57.7 ·
Código fuente
github.com/wevm/viem
Documentación
viem.sh/

Compatibilidad de un vistazo

ECDSAEthereum y Bitcoin

Curvas ECDSA
secp256k1
EdDSA
none

viem es una biblioteca tipada y compatible con tree-shaking para clientes, cuentas, contratos y codificación ABI de Ethereum, y la base de wagmi. Las cuentas locales de privateKeyToAccount() firman con @noble/curves (viem 2.57.7 depende de su versión 1.9.1), así que sus firmas son deterministas según RFC 6979 y low-S: idénticas, para la misma clave y el mismo mensaje, a las que produce ethers.

account.signMessage() aplica el prefijo EIP-191 y keccak-256 y devuelve 65 bytes que terminan en v = 27 o 28; sign() con un parámetro hash firma un resumen de 32 bytes sin modificarlo, que es como se firman los resúmenes EIP-712 y los hashes de transacciones. La comprobación contrastó ambos con implementaciones independientes.

La verificación es más permisiva que en ethers. recoverMessageAddress() y la utilidad verifyMessage aceptaron la firma high-S reflejada, que es matemáticamente válida, pero es una forma que ethers rechaza con non-canonical s. Si tu sistema deduplica o almacena firmas, normaliza s tú mismo; de lo contrario, se aceptará una segunda firma, de aspecto distinto, para el mismo mensaje. En sentido contrario, la forma compacta EIP-2098 de 64 bytes falló con invalid signature length en ambas funciones; expándela antes con parseCompactSignature y compactSignatureToSignature.

La utilidad independiente verifyMessage solo comprueba cuentas de propiedad externa. Las billeteras de contrato inteligente (Safe, billeteras de abstracción de cuentas, billeteras con llave de acceso) firman mediante ERC-1271 o ERC-6492, y solo la acción verifyMessage de un cliente público consulta al contrato. Inspecciona los datos tipados con el inspector de EIP-712, los mensajes de inicio de sesión con el verificador de SIWE y las firmas en bruto con el verificador de firmas de Ethereum.

El ejemplo firma un mensaje de inicio de sesión y lo comprueba de dos maneras: mediante la verificación contra la dirección y mediante la recuperación del firmante.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/viem/run.sh ejecutó viem 2.57.7 en Node.js 24.22.0; viem depends on @noble/curves 1.9.1 en la imagen de contenedor node:24.22.0-bookworm-slim el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    rsv-65

    signMessage() devuelve 65 bytes en hexadecimal: r, s y v.

    Registrado por la comprobación
    fact ecdsa-signature-encoding = rsv-65
  • ID de recuperación v

    27-28

    v es 27 o 28; parseSignature() también lo expresa como yParity 0 o 1.

    Registrado por la comprobación
    fact recovery-v = 27-28
  • Prefijo del mensaje

    eip-191

    hashMessage() es igual al keccak-256 del prefijo EIP-191, la longitud en decimal y el mensaje, calculado de forma independiente.

    Registrado por la comprobación
    fact message-prefix = eip-191
  • Hash predeterminado

    keccak256

    Las firmas de mensajes usan keccak-256.

    Registrado por la comprobación
    fact ecdsa-default-hash = keccak256
  • De qué calcula el hash la llamada de firma

    hashes-message-or-digest

    signMessage() calcula el hash con el prefijo EIP-191; sign() con un parámetro hash firma un resumen de 32 bytes sin modificarlo y produjo la misma firma para ese resumen.

    Registrado por la comprobación
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: sign() over the EIP-191 digest equals signMessage(): true
  • Nonce de ECDSA

    rfc6979

    RFC 6979: la firma coincidió con la firma determinista de @noble/curves sobre el mismo resumen.

    Registrado por la comprobación
    fact ecdsa-nonce = rfc6979
  • Low-S al firmar

    low-s

    Ningún valor high-S entre 64 firmas de mensajes.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = low-s
  • High-S al verificar

    accepts-high-s

    Con la firma reflejada (r, n − s) y v invertido, recoverMessageAddress() recuperó al firmante y la utilidad verifyMessage la verificó.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: recoverMessageAddress with (r, n - s, flipped v): recovers the signer
  • Formas de firma aceptadas

    v-27-28v-0-1

    A partir de firmas de 65 bytes con v = 27/28 o v = 0/1 se recuperó al firmante; la forma EIP-2098 de 64 bytes falló con invalid signature length.

    Registrado por la comprobación
    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

    Solo secp256k1.

    Registrado por la comprobación
    fact ecdsa-curves = secp256k1
  • EdDSA

    none

    Sin EdDSA.

    Registrado por la comprobación
    fact eddsa = none
  • Algoritmos poscuánticos

    none

    Sin algoritmos poscuánticos.

    Registrado por la comprobación
    fact pqc = none
  • Formatos de clave pública

    sec1-uncompressed

    account.publicKey es el punto SEC1 de 65 bytes en formato sin comprimir.

    Registrado por la comprobación
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: account.publicKey is 65 bytes starting with 0x04: true

Uso

Firma un mensaje de inicio de sesión con una cuenta local; después, verifícalo contra la dirección y recupera al firmante.
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);

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor node:24.22.0-bookworm-slim, y la ejecución falla si la salida no coincide.

Configuración
npm init -y >/dev/null && npm install --no-audit --no-fund viem@2.57.7
Comando
node usage.mts
La salida debe coincidir (expresión regular)
signature bytes: 65\nvalid: true signer matches: true

Bibliotecas