Überprüfung der personal.sign-Signatur mit Pyethereum

Ich versuche, eine signierte Nachricht von Metamask mit Pyethereum zu verifizieren. Ich kann anscheinend nicht die richtige Adresse aus der Signatur wiederherstellen. Mangelnde Dokumentation hilft nicht.

Auf der Clientseite habe ich eine Metamask-Signierung mit web3.personal.sign().

var signer = web3.eth.defaultAccount || web3.eth.accounts[0];
var original_message = "I am but a stack exchange post";
var message = "0x" + original_message.toHex();
var message_hash = web3.sha3('\u0019Ethereum Signed Message:\n' + message.length.toString() + message);
var signature;
web3.personal.sign(message, signer, function(err, res) {
    if (err) console.error(err);
    signature = res;
    console.log({
        "signer": signer,
        "message": message,
        "message_hash": message_hash,
        "signature": signature,
    })
});

{
    message: "0x4920616d20627574206120737461636b2065786368616e676520706f7374"
    message_hash: "0x1a0126ceafb4579293016a4cc3ca0ec753c7d497cda8b3e6ece095c832d92590"
    signature: "0x0cf7e2e1cbaf249175b8e004118a182eb378a0b78a7a741e72a0a34e970b59194aa4d9419352d181a4d1827abbad279ad4f5a7b60da5751b82fec4dde6f380a51b"
    signer: "0x9283099a29556fcf8fff5b2cea2d4f67cb7a7a8b"
}

Dann sende ich die Signatur, den Hash der Nachricht und die Adresse an das Backend, wo ich so etwas habe:

>>> from ethereum.utils import ecrecover_to_pub, sha3
>>> from eth_utils.hexidecimal import encode_hex, decode_hex, add_0x_prefix
>>> signer = "0x9283099a29556fcf8fff5b2cea2d4f67cb7a7a8b"
>>> message_hash = "0x1a0126ceafb4579293016a4cc3ca0ec753c7d497cda8b3e6ece095c832d92590"
>>> signature = "0x0cf7e2e1cbaf249175b8e004118a182eb378a0b78a7a741e72a0a34e970b59194aa4d9419352d181a4d1827abbad279ad4f5a7b60da5751b82fec4dde6f380a51b"
>>> 
>>> r = int(signature[0:66], 16)
>>> s = int(add_0x_prefix(signature[66:130]), 16)
>>> v = int(add_0x_prefix(signature[130:132]), 16)
>>> if v not in (27,28):
...     v += 27
... 
>>> pubkey = ecrecover_to_pub(decode_hex(message_hash), v, r, s)
>>> assert(encode_hex(sha3(pubkey)[-20:]) == signer)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Ich habe ein paar Möglichkeiten ausprobiert, um mit der Codierung herumzuspielen, aber ich glaube, mir fehlt immer noch etwas (hoffentlich offensichtlich). Irgendwelche Ideen?

Haben Sie der Nachricht das Präfix vorangestellt, bevor Sie sie signiert haben? Präfix hier: web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#sign
@carver web3js erledigt das automatisch. "Diese Daten werden vor UTF-8 HEX dekodiert und wie folgt umhüllt"
Entschuldigung, ich habe mich vertan. Was ich meinte war, als Sie den Hash der Nachricht vor der Wiederherstellung neu generiert haben, haben Sie das Präfix eingeschlossen? Das sieht so aus, als ob es vielleicht nicht so wäre: web3.sha3(message). Sie können web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#hashmessage verwenden, wodurch das Präfix entfernt wird. (Außerdem wird web3.py bald mit nativen Tools dafür herauskommen)
Sieht auch message_hash.encode('utf-8')falsch aus, weil es sich um eine Hex-codierte Zeichenfolge handelt. Sie wollen wahrscheinlich so etwas wie codecs.decode(message_hash[2:], 'hex'). Es gefällt Ihnen vielleicht eth_utils.decode_hex(message_hash)besser, wenn Sie offen für eine Abhängigkeit von sind ethereum-utils.
Wenn Sie die ursprüngliche Nachricht posten, können wir das Präfix überprüfen.
@carver Das sind alles großartige Informationen, danke. Ich habe meinen Beitrag mit Ihren Korrekturen aktualisiert. Allerdings sehe ich das Problem immer noch. Wenn Sie weitere Ideen haben, würde ich sie gerne hören.

Antworten (1)

Alles in Python sieht korrekt aus. Der Nachrichten-Hash in JavaScript wird falsch generiert.

Vorbereiten des Nachrichten-Hash in Javascript

Der Nachrichten-Hash muss unter Verwendung der ursprünglichen Nachricht generiert werden, ohne sie zuerst in Hex-Kodierung zu versetzen.

In der Praxis bedeutet das, dieses JavaScript zu ersetzen:

var message = "0x" + original_message.toHex();
var message_hash = web3.sha3(
  '\u0019Ethereum Signed Message:\n' +
  message.length.toString() +
  message
);

mit diesem:

var message_hash = web3.sha3(
  '\u0019Ethereum Signed Message:\n' +
  original_message.length.toString() +
  original_message
);

Das gibt Ihnen den Nachrichten-Hash:0x6e099d83ea72d1ef62e39a501fe000c1458ba5a511510a0e9348b0dfeb298803

Wenn Sie diesen Nachrichten-Hash verwenden, stellen Sie den richtigen Unterzeichner wieder her:0x9283099a29556fcf8fff5b2cea2d4f67cb7a7a8b

Eine noch bessere Lösung:hashMessage()

Mit web3.js v1 können Sie Folgendes aufrufen:
message_hash = web3.eth.accounts.hashMessage("I am but a stack exchange post").

V1 befindet sich seit Oktober 2017 in der Beta-Phase


Ein weiterer häufiger Fehler

(aus einem früheren Entwurf der Frage)

Weglassen des Präfixes beim Hashen der Nachricht zur Wiederherstellung.

Da web3.personal.sign()das Präfix für Sie hinzugefügt wird, kann das Präfix beim Hashen der Nachricht zur Wiederherstellung leicht vergessen werden. Die beste Lösung ist wiederum die Verwendung von web3.js v1 für web3.eth.accounts.hashMessage().


Die einfachste Möglichkeit

Ab Web3.py v4 gibt es eine integrierte Unterstützung zum Wiederherstellen des Nachrichtenunterzeichners , wie zum Beispiel:

from web3.auto import w3

# If you have the original message, you need to hash it first
from eth_account.messages import defunct_hash_message
message_hash = defunct_hash_message(text=original_message)

# If you begin with the message hash, start here:
signer = w3.eth.account.recoverHash(message_hash, signature=signature)

Warum heißt es defunct_hash_message? -- Leider wird der Nachrichtenstandard nicht gut unterstützt. Es hat leicht unterschiedliche Implementierungen in einer Reihe von Knoten und Hardware-Clients. Es gibt einige neue Nachrichtenformate, die derzeit diskutiert werden und hoffentlich bald eine breitere und konsistentere Akzeptanz finden werden. Derzeit unterstützt Web3.py nur ausdrücklich das Nachrichtenformat im Geth-Stil, indem die defunct_hash_messageMethode verwendet wird.

2 mögliche Probleme, auf die ich stoße. web3.personal.signerfordert, dass die Nachricht hexadezimal codiert ist ( HookedWalletSubprovider - validateMessage - message was not encoded as hex.), weshalb ich 0xdie Nachricht hinzugefügt und in hexadezimal konvertiert habe. Oder gut, zumindest Metamask erfordert es. Auch web3.eth.accounts.hashMessageist es noch nicht allgemein verfügbar, aber ich freue mich darauf. Danke für die ganzen Infos.
Ergänzung: Sie kommen genau auf mein Problem zu. Das Ändern des Anrufs in web3.personal.sign("0x" + message.toHex()...und das Verwenden der einfachen Nachricht beim Hashen funktioniert hervorragend. Danke noch einmal!