Verify Metamask signature (ethereum) using Python
Asked Answered
J

2

6

I would like to verify an ethereum (ETH) signature made in MetaMask using python. I'm developing a website using flask as backend. Javascript code send a POST requests to the back end containing the 3 following variables:

{'signature': '0x0293cc0d4eb416ca95349b7e63dc9d1c9a7aab4865b5cd6d6f2c36fb1dce12d34a05039aedf0bc64931a439def451bcf313abbcc72e9172f7fd51ecca30b41dd1b', 'nonce': '6875972781', 'adress': '0x3a806c439805d6e0fdd88a4c98682f86a7111789'}

My goal is to verify that the signature contains the nonce (random integer) and was sign by the public adress

I using javascript to sign the nonce using the ether library

const ethereum = window.ethereum;
const provider = new ethers.providers.Web3Provider(ethereum)
const signer = provider.getSigner()
var signature = await signer.signMessage(nonce);

I tried with several python libraires, but I'm unable to format signature, adress and nonce so that it works. here is unsuccessfull try made using ecdsa librairy:

vk = ecdsa.VerifyingKey.from_string(bytes.fromhex(address), curve=ecdsa.SECP256k1, hashfunc=sha256) 
vk.verify(bytes.fromhex(hex(signature)), bytes(nonce, 'utf-8'))

I get the following error:

ValueError: non-hexadecimal number found in fromhex() arg at position 1

Thanks for your help !

Joellejoellen answered 21/3, 2021 at 18:30 Comment(2)
Here is some Python signing and verifying code I did back in a day github.com/TokenMarketNet/smart-contracts/blob/master/ico/…Freezing
did you ever find a solution to this?Alanna
P
6

Using web3.py you could use w3.eth.account.recover_message to recover the address from the signature and the data. After that you compare the adress to the correct adress(with lowercase, because i think web3.py would give you lower and uppercase)

from web3 import Web3
from hexbytes import HexBytes
from eth_account.messages import encode_defunct
w3 = Web3(Web3.HTTPProvider(""))
mesage= encode_defunct(text="6875972781")
address = w3.eth.account.recover_message(mesage,signature=HexBytes("0x0293cc0d4eb416ca95349b7e63dc9d1c9a7aab4865b5cd6d6f2c36fb1dce12d34a05039aedf0bc64931a439def451bcf313abbcc72e9172f7fd51ecca30b41dd1b"))
print(address)
Pastorale answered 14/2, 2022 at 12:19 Comment(2)
I use encode_defunct as well but I can't help thinking about what the method's description says, "Encode a message for signing, using an old, unrecommended approach.. Only use this method if you must have compatibility with :meth:w3.eth.sign() <web3.eth.Eth.sign>.". Do you know of any alternates?Bonne
that's because you use: var signature = await signer.signMessage(nonce); another option, i think is use in js var signature = await signer._signTypedData from docs.ethers.io/v5/api/signer and then encode_structured_data from eth-account.readthedocs.io/en/latest/… in that case is not a string to sign, is a eip712 estructured data. But eip721 is not approved, and the two methods are experimentals.Pastorale
B
2

You can verify using JS but I commend you for wanting to verify on the backend with python. I was faced with the exact same choice and opted for the latter. Basically, I created 2 apis, the first of which generates the message and nonce and the second verifies the signature.

I'm using FastAPI for this but you can pattern it to any framework you prefer such as Django.

Generate message:

# ethaccount is the user's wallet included in the body of the request
async def generate_message(ethaccount: str = Body(...)) -> str:
    # I save the wallet to cache for reference later
    set_cache_address(ethaccount)

    # Generate the nonce
    nonce = uuid.uuid4()

    # Generate the message
    message = f'''
Welcome! Sign this message to login to the site. This doesn't cost you
anything and is free of any gas fees.

Nonce:
{nonce}.
    '''
    return message

Metamask takes over from here. Afterwards verify the signature generated by metamask:

from web3.auto import w3
from eth_account.messages import encode_defunct

async def signature(data: dict = Body(...)):     # noqa
    # User's signature from metamask passed through the body
    sig = data.get('signature')

    # The juicy bits. Here I try to verify the signature they sent.
    message = encode_defunct(text=data.get('message'))
    signed_address = (w3.eth.account.recover_message(message, signature=sig)).lower()

    # Same wallet address means same user. I use the cached address here.
    if get_cache_address() == signed_address:
        # Do what you will
        # You can generate the JSON access and refresh tokens here
        pass

NOTE: I've cleaned this code to show only the logic/methods you might need. The actual code I'm using is longer as I generate tokens, etc.

Bonne answered 24/4, 2022 at 13:8 Comment(1)
what's the (data: dict = Body(...)) Can i use request, and do i need to define a Body. about to run this codeSailplane

© 2022 - 2024 — McMap. All rights reserved.