Zum Inhalt springen
ecdsa.com
Node.js · WebCrypto

viem

viem von wevm, die TypeScript-Schnittstelle zu Ethereum, die wagmi nutzt: 65-Byte-Signaturen r‖s‖v mit v = 27/28, Hashing nach EIP-191 und RFC 6979 – aber anders als ethers stellt viem den Signierer auch aus High-S-Signaturen wieder her und liest keine kompakten Signaturen nach EIP-2098.
Lizenz
MIT
Geprüfte Version
2.57.7 ·
Quellcode
github.com/wevm/viem
Dokumentation
viem.sh/

Kompatibilität auf einen Blick

ECDSAEthereum und Bitcoin

ECDSA-Kurven
secp256k1
EdDSA
none

viem ist eine typisierte, Tree-Shaking-fähige Bibliothek für Ethereum-Clients, Konten, Contracts und ABI-Kodierung und die Grundlage von wagmi. Lokale Konten aus privateKeyToAccount() signieren mit @noble/curves (viem 2.57.7 hängt von dessen Version 1.9.1 ab), ihre Signaturen sind also deterministisch nach RFC 6979 und Low-S – bei gleichem Schlüssel und gleicher Nachricht identisch mit dem, was ethers erzeugt.

account.signMessage() wendet das Präfix nach EIP-191 und keccak-256 an und gibt 65 Byte zurück, die auf v = 27 oder 28 enden; sign() mit dem Parameter hash signiert einen 32-Byte-Digest unverändert – so werden EIP-712-Digests und Transaktions-Hashes signiert. Die Prüfung bestätigte beides mit unabhängigen Implementierungen.

Die Prüfung ist nachsichtiger als in ethers. recoverMessageAddress() und die Hilfsfunktion verifyMessage akzeptierten die gespiegelte High-S-Signatur – mathematisch gültig, aber eine Form, die ethers mit non-canonical s ablehnt. Wenn dein System Signaturen dedupliziert oder speichert, normalisiere s selbst, sonst wird eine zweite, anders aussehende Signatur für dieselbe Nachricht akzeptiert. Umgekehrt scheiterte die kompakte 64-Byte-Form nach EIP-2098 in beiden Funktionen mit invalid signature length; expandiere sie vorher mit parseCompactSignature und compactSignatureToSignature.

Die eigenständige Hilfsfunktion verifyMessage prüft nur Konten mit eigenem Schlüssel (EOAs). Smart-Contract-Wallets (Safe, Account-Abstraction-Wallets, Passkey-Wallets) signieren über ERC-1271 oder ERC-6492, und nur die Action verifyMessage eines Public Clients fragt den Contract. Untersuche Typed Data mit dem EIP-712-Inspektor, Anmeldenachrichten mit dem SIWE-Verifizierer und rohe Signaturen mit dem Ethereum-Signatur-Verifizierer.

Das Beispiel signiert eine Anmeldenachricht und prüft sie auf zwei Wegen: durch Prüfung gegen die Adresse und durch Wiederherstellung des Signierers.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/viem/run.sh hat viem 2.57.7 unter Node.js 24.22.0; viem depends on @noble/curves 1.9.1 am 20. Oktober 2026 im Container-Image node:24.22.0-bookworm-slim ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    rsv-65

    signMessage() gibt 65 Byte als Hex zurück: r, s und v.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = rsv-65
  • Recovery-ID v

    27-28

    v ist 27 oder 28; parseSignature() gibt es zusätzlich als yParity 0 oder 1 aus.

    Von der Prüfung aufgezeichnet
    fact recovery-v = 27-28
  • Nachrichtenpräfix

    eip-191

    hashMessage() entspricht keccak-256 über das Präfix nach EIP-191, die dezimale Länge und die Nachricht, unabhängig berechnet.

    Von der Prüfung aufgezeichnet
    fact message-prefix = eip-191
  • Standard-Hash

    keccak256

    Nachrichtensignaturen verwenden keccak-256.

    Von der Prüfung aufgezeichnet
    fact ecdsa-default-hash = keccak256
  • Was der Signaturaufruf hasht

    hashes-message-or-digest

    signMessage() hasht mit dem Präfix nach EIP-191; sign() mit dem Parameter hash signiert einen 32-Byte-Digest unverändert und erzeugte für diesen Digest die identische Signatur.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: sign() over the EIP-191 digest equals signMessage(): true
  • ECDSA-Nonce

    rfc6979

    RFC 6979: Die Signatur stimmte mit der deterministischen Signatur von @noble/curves über denselben Digest überein.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = rfc6979
  • Low-S beim Signieren

    low-s

    Kein High-S-Wert unter 64 Nachrichtensignaturen.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = low-s
  • High-S bei der Prüfung

    accepts-high-s

    Aus der gespiegelten Signatur (r, n − s) mit umgeschaltetem v stellte recoverMessageAddress() den Signierer wieder her, und in der Hilfsfunktion verifyMessage bestand sie die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: recoverMessageAddress with (r, n - s, flipped v): recovers the signer
  • Akzeptierte Signaturformen

    v-27-28v-0-1

    Aus 65-Byte-Signaturen mit v = 27/28 oder v = 0/1 ließ sich der Signierer wiederherstellen; die 64-Byte-Form nach EIP-2098 scheiterte mit invalid signature length.

    Von der Prüfung aufgezeichnet
    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
  • ECDSA-Kurven

    secp256k1

    Nur secp256k1.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = secp256k1
  • EdDSA

    none

    Kein EdDSA.

    Von der Prüfung aufgezeichnet
    fact eddsa = none
  • Post-Quanten-Algorithmen

    none

    Keine Post-Quanten-Algorithmen.

    Von der Prüfung aufgezeichnet
    fact pqc = none
  • Formate öffentlicher Schlüssel

    sec1-uncompressed

    account.publicKey ist der unkomprimierte Punkt im SEC1-Format mit 65 Byte.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = sec1-uncompressed
    evidence public-key-formats: account.publicKey is 65 bytes starting with 0x04: true

Verwendung

Signiere eine Anmeldenachricht mit einem lokalen Konto, prüfe sie dann gegen die Adresse und stelle den Signierer wieder her.
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);

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image node:24.22.0-bookworm-slim aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Einrichtung
npm init -y >/dev/null && npm install --no-audit --no-fund viem@2.57.7
Befehl
node usage.mts
Ausgabe muss passen (regulärer Ausdruck)
signature bytes: 65\nvalid: true signer matches: true

Bibliotheken