Estoy tratando de verificar un mensaje firmado de Metamask con pyethereum. Parece que no puedo recuperar la dirección correcta de la firma. La falta de documentación no ayuda.
En el lado del cliente, tengo metamask firmando con 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"
}
Luego envío la firma, el hash del mensaje y la dirección al backend, donde tengo algo como esto:
>>> 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
He intentado algunas formas de jugar con la codificación, pero creo que todavía me falta algo (con suerte, obvio). ¿Algunas ideas?
Todo en Python parece correcto. El hash del mensaje en JavaScript se genera incorrectamente.
El hash del mensaje debe generarse usando el mensaje original, sin codificarlo primero en hexadecimal.
En la práctica, eso significa reemplazar este JavaScript:
var message = "0x" + original_message.toHex();
var message_hash = web3.sha3(
'\u0019Ethereum Signed Message:\n' +
message.length.toString() +
message
);
con este:
var message_hash = web3.sha3(
'\u0019Ethereum Signed Message:\n' +
original_message.length.toString() +
original_message
);
Eso te dará el hash del mensaje:0x6e099d83ea72d1ef62e39a501fe000c1458ba5a511510a0e9348b0dfeb298803
Cuando use ese hash de mensaje, recuperará el firmante correcto:0x9283099a29556fcf8fff5b2cea2d4f67cb7a7a8b
Una solución aún mejor:hashMessage()
Con web3.js v1, puede llamar:
message_hash = web3.eth.accounts.hashMessage("I am but a stack exchange post")
.
V1 está en Beta, a partir de octubre de 2017
(de un borrador anterior de la pregunta)
Omitir el prefijo al codificar el mensaje para la recuperación.
Debido a que web3.personal.sign()
agrega el prefijo por usted, es fácil olvidar el prefijo al codificar el mensaje para la recuperación. La mejor solución es volver a utilizar web3.js v1 para web3.eth.accounts.hashMessage()
.
A partir de Web3.py v4, hay soporte integrado para recuperar el firmante del mensaje , como:
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)
¿Por qué se llama defunct_hash_message
? -- Desafortunadamente, el estándar de mensajes no está bien soportado. Tiene implementaciones ligeramente diferentes en varios nodos y clientes de hardware. Hay algunos formatos de mensajes nuevos que se están debatiendo y, con suerte, pronto obtendrán una adopción más amplia y consistente. Por ahora, Web3.py solo admite explícitamente el formato de mensaje de estilo geth, utilizando el defunct_hash_message
método.
web3.personal.sign
requiere que el mensaje esté codificado en hexadecimal( HookedWalletSubprovider - validateMessage - message was not encoded as hex.
), razón por la cual estaba agregando 0x
y convirtiendo el mensaje a hexadecimal. O bueno, al menos metamask lo requiere. Además, web3.eth.accounts.hashMessage
aún no está ampliamente disponible, pero lo espero con ansias. Gracias por toda la información.web3.personal.sign("0x" + message.toHex()...
y simplemente usar el mensaje simple cuando el hashing funciona muy bien. ¡Gracias de nuevo!
tallista
mike shurtz
tallista
web3.sha3(message)
. Puede usar web3js.readthedocs.io/en/1.0/web3-eth-accounts.html#hashmessage que abstrae el prefijo. (Además, web3.py saldrá pronto con herramientas nativas para esto)tallista
message_hash.encode('utf-8')
se ve mal porque es una cadena codificada en hexadecimal. Probablemente quieras algo comocodecs.decode(message_hash[2:], 'hex')
. Puede que le gusteeth_utils.decode_hex(message_hash)
más, si está abierto a una dependencia deethereum-utils
.tallista
mike shurtz