Aller au contenu
ecdsa.com
Node.js · WebCrypto

viem

viem de wevm, l’interface TypeScript pour Ethereum qu’utilise wagmi : signatures r‖s‖v de 65 octets avec v = 27/28, hachage EIP-191 et RFC 6979, mais, contrairement à ethers, viem récupère le signataire à partir de signatures high-S et ne lit pas les signatures compactes EIP-2098.
Licence
MIT
Version vérifiée
2.57.7 ·
Code source
github.com/wevm/viem
Documentation
viem.sh/

Compatibilité en un coup d’œil

ECDSAEthereum et Bitcoin

Courbes ECDSA
secp256k1
EdDSA
none
Formats de clé publique
sec1-uncompressed

viem est une bibliothèque typée et compatible avec le tree shaking pour les clients, les comptes, les contrats et l’encodage ABI d’Ethereum, et sert de fondation à wagmi. Les comptes locaux issus de privateKeyToAccount() signent avec @noble/curves (viem 2.57.7 dépend de sa version 1.9.1) : leurs signatures sont donc déterministes (RFC 6979) et low-S, identiques, pour une même clé et un même message, à celles que produit ethers.

account.signMessage() applique le préfixe EIP-191 et keccak-256, puis renvoie 65 octets terminés par v = 27 ou 28 ; sign() avec un paramètre hash signe tel quel un condensat de 32 octets, ce qui permet de signer les condensats EIP-712 et les hash de transaction. Les essais ont confirmé ces deux comportements avec des implémentations indépendantes.

La vérification est plus permissive que dans ethers. recoverMessageAddress() et l’utilitaire verifyMessage ont accepté la signature miroir high-S, mathématiquement valide, mais d’une forme qu’ethers rejette avec non-canonical s. Si votre système dédoublonne ou stocke des signatures, normalisez s vous-même, sinon une seconde signature, d’apparence différente, sera acceptée pour le même message. Dans l’autre sens, la forme compacte EIP-2098 de 64 octets a échoué avec invalid signature length dans les deux fonctions : étendez-la d’abord avec parseCompactSignature et compactSignatureToSignature.

L’utilitaire autonome verifyMessage ne vérifie que les comptes détenus en externe. Les portefeuilles de contrat intelligent (Safe, portefeuilles à abstraction de compte, portefeuilles à clé d’accès) signent via ERC-1271 ou ERC-6492, et seule l’action verifyMessage d’un client public interroge le contrat. Examinez les données typées avec l’inspecteur EIP-712, les messages de connexion avec le vérificateur SIWE et les signatures brutes avec le vérificateur de signatures Ethereum.

L’exemple signe un message de connexion et le contrôle de deux façons : par une vérification par rapport à l’adresse et par la récupération du signataire.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/viem/run.sh a exécuté viem 2.57.7 sur Node.js 24.22.0; viem depends on @noble/curves 1.9.1 dans l’image de conteneur node:24.22.0-bookworm-slim le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    rsv-65

    signMessage() renvoie 65 octets en hexadécimal : r, s et v.

    Relevé par la vérification
    fact ecdsa-signature-encoding = rsv-65
  • Identifiant de récupération v

    27-28

    v vaut 27 ou 28 ; parseSignature() l’indique aussi sous la forme yParity 0 ou 1.

    Relevé par la vérification
    fact recovery-v = 27-28
  • Préfixe de message

    eip-191

    hashMessage() est égal au keccak-256 du préfixe EIP-191, de la longueur en décimal et du message, calculé de façon indépendante.

    Relevé par la vérification
    fact message-prefix = eip-191
  • Fonction de hachage par défaut

    keccak256

    Les signatures de messages utilisent keccak-256.

    Relevé par la vérification
    fact ecdsa-default-hash = keccak256
  • Ce que l’appel de signature hache

    hashes-message-or-digest

    signMessage() hache avec le préfixe EIP-191 ; sign() avec un paramètre hash signe tel quel un condensat de 32 octets et a produit une signature identique pour ce condensat.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: sign() over the EIP-191 digest equals signMessage(): true
  • Nonce ECDSA

    rfc6979

    RFC 6979 : la signature correspondait à la signature déterministe de @noble/curves sur le même condensat.

    Relevé par la vérification
    fact ecdsa-nonce = rfc6979
  • Low-S à la signature

    low-s

    Aucune valeur high-S parmi 64 signatures de messages.

    Relevé par la vérification
    fact ecdsa-low-s-sign = low-s
  • High-S à la vérification

    accepts-high-s

    La signature miroir (r, n − s) avec v inversé a permis de récupérer le signataire dans recoverMessageAddress() et a été vérifiée par l’utilitaire verifyMessage.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: recoverMessageAddress with (r, n - s, flipped v): recovers the signer
  • Formes de signature acceptées

    v-27-28v-0-1

    Les signatures de 65 octets avec v = 27/28 ou v = 0/1 ont permis de récupérer le signataire ; la forme EIP-2098 de 64 octets a échoué avec invalid signature length.

    Relevé par la vérification
    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
  • Courbes ECDSA

    secp256k1

    secp256k1 uniquement.

    Relevé par la vérification
    fact ecdsa-curves = secp256k1
  • EdDSA

    none

    Pas d’EdDSA.

    Relevé par la vérification
    fact eddsa = none
  • Algorithmes post-quantiques

    none

    Aucun algorithme post-quantique.

    Relevé par la vérification
    fact pqc = none
  • Formats de clé publique

    sec1-uncompressed

    account.publicKey est le point non compressé SEC1 de 65 octets.

    Relevé par la vérification
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: account.publicKey is 65 bytes starting with 0x04: true

Utilisation

Signer un message de connexion avec un compte local, puis le vérifier par rapport à l’adresse et récupérer le signataire.
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);

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur node:24.22.0-bookworm-slim, et l’exécution échoue si la sortie ne correspond pas.

Mise en place
npm init -y >/dev/null && npm install --no-audit --no-fund viem@2.57.7
Commande
node usage.mts
La sortie doit correspondre à cette expression régulière
signature bytes: 65\nvalid: true signer matches: true

Bibliothèques