Zum Inhalt springen
ecdsa.com
Python

web3.py (eth-account)

web3.py der Ethereum Foundation, bei dem eth-account das Signieren übernimmt: 65-Byte-Signaturen r‖s‖v mit v = 27/28, EIP-191 und RFC 6979 – und wie bei viem, aber anders als bei ethers, die Wiederherstellung des Signierers auch aus High-S-Signaturen.
Lizenz
MIT
Geprüfte Version
8.0.0 ·
Quellcode
github.com/ethereum/web3.py
Dokumentation
web3py.readthedocs.io/en/stable/

Kompatibilität auf einen Blick

ECDSAEthereum und Bitcoin

ECDSA-Kurven
secp256k1
EdDSA
none

web3.py ist der Python-Client für Ethereum-Knoten; das Signieren von Nachrichten und Transaktionen übernimmt das Paket eth-account (0.14.0 mit web3 8.0.0), das für die Kurvenarithmetik wiederum eth-keys verwendet. Der meiste Signiercode importiert eth_account direkt, so wie das Beispiel.

Account.sign_message(encode_defunct(text=...)) implementiert personal_sign: das Präfix nach EIP-191 mit der dezimalen Bytelänge, keccak-256, eine Nonce nach RFC 6979 (abgeglichen mit einem unabhängigen Referenz-Signierer) und s in der unteren Hälfte, zurückgegeben als 65 Byte, die auf v = 27 oder 28 enden. encode_defunct ist der Teil, den viele vergessen: Wer rohe Bytes mit unsafe_sign_hash signiert, lässt das Präfix weg – und genau das macht eine Signatur anderswo als Anmeldenachweis unbrauchbar.

Account.recover_message() stellte den Signierer aus der gespiegelten High-S-Signatur ebenso wieder her wie aus v = 0/1 und akzeptiert damit mehr Formen als ethers – normalisiere s selbst, wenn Signaturen gespeichert oder dedupliziert werden. Die kompakte 64-Byte-Form nach EIP-2098 wurde mit Unexpected recoverable signature length: Expected 65, but got 64 bytes abgelehnt; expandiere sie vor der Wiederherstellung.

Für Typed Data verwende encode_typed_data (EIP-712), statt das JSON selbst zu hashen, und prüfe mit dem EIP-712-Inspektor, was eine Wallet signieren soll. Smart-Contract-Wallets brauchen statt recover_message einen Aufruf des Contracts nach ERC-1271; Fehler in Anmeldenachrichten findest du am einfachsten mit dem SIWE-Verifizierer.

Das Beispiel signiert eine Anmeldenachricht und vergleicht die wiederhergestellte Adresse – der Vergleich, der jeden byteweisen Abgleich von Signaturen ersetzen sollte.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/web3py/run.sh hat web3.py (eth-account) 8.0.0 am 20. Oktober 2026 im Container-Image python:3.14.8-slim-trixie 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

    sign_message() gibt 65 Byte 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.

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

    eip-191

    Der Digest entspricht keccak-256 über \x19Ethereum Signed Message:\n, 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

    sign_message() hasht mit dem Präfix nach EIP-191; unsafe_sign_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: unsafe_sign_hash over the EIP-191 digest equals sign_message: True
  • ECDSA-Nonce

    rfc6979

    RFC 6979: Die Signatur stimmte bei normalisiertem s mit einer unabhängigen Referenzimplementierung ü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

    recover_message() stellte den Signierer aus (r, n − s) mit umgeschaltetem v wieder her.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: recover_message on (r, n - s, flipped v): 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 löste ValidationError: Unexpected recoverable signature length: Expected 65, but got 64 bytes aus.

    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: ValidationError: Unexpected recoverable signature length: Expected 65, but got 64 bytes
  • 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

Verwendung

Signiere eine Anmeldenachricht mit der Semantik von personal_sign und stelle die Adresse des Signierers wieder her.
from eth_account import Account
from eth_account.messages import encode_defunct

account = Account.create()
message = encode_defunct(text="Sign in to example.com\nNonce: 8f2c1a")

# personal_sign: EIP-191 prefix + keccak-256, RFC 6979, low-S, v = 27 or 28.
signed = account.sign_message(message)

# Recover and compare addresses; checksummed strings compare exactly.
signer = Account.recover_message(message, signature=signed.signature)
print("signature bytes:", len(signed.signature), "v:", signed.v)
print("signer matches:", signer == account.address)

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

Einrichtung
pip install --quiet --root-user-action=ignore web3==8.0.0 eth-account==0.14.0
Befehl
python usage.py
Ausgabe muss passen (regulärer Ausdruck)
signature bytes: 65 v: 2[78]\nsigner matches: True

Bibliotheken