'Verify Metamask signature (ethereum) using Python

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 !



Solution 1:[1]

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)

Solution 2:[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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pablo Estevez
Solution 2 enchance