Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate web3 signed message #40

Closed
Netherdrake opened this issue Mar 20, 2018 · 21 comments
Closed

Validate web3 signed message #40

Netherdrake opened this issue Mar 20, 2018 · 21 comments

Comments

@Netherdrake
Copy link

Netherdrake commented Mar 20, 2018

Is it possible to use eth-keys to validate web3 (Metamask backend) signed messages?

I've been trying to piece it together from documentation, but no luck so far.
https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
https://github.com/ethereum/wiki/wiki/JavaScript-API#web3ethsign

Code (only guessing so far):

def recover_address(data: str, signature: str) -> str:
    signature = binascii.unhexlify(remove_0x_prefix(signature))
    data = keccak(text=data)
    data = f"\\x19Ethereum Signed Message:\n{len(data)}{data}"
    data = bytes(data, 'ascii')

    # web3js outputs in rsv order
    vrs = (
        ord(signature[64:65]) - 27,
        big_endian_to_int(signature[0:32]),
        big_endian_to_int(signature[32:64]),
    )
    sig = KeyAPI.Signature(vrs=vrs)
    return sig.recover_public_key_from_msg(data).to_address()

Web3js sign example:

let address = '0x5B71759d4666c6674832352DA09f15efC8414EAE';
web3.eth.sign(address, web3.sha3('foo'), (e, r) => console.log(r))

Expectation:

recover_address('foo', r)
# should output 0x5B71759d4666c6674832352DA09f15efC8414EAE
@pipermerriam
Copy link
Member

If my memory is correct:

Short answer is yes, but you have to do some manipulation of the v value since ethereum uses a non-standard v of 27/28 and eth-keys uses what we refer to as the canonical v of 0/1.

You should take a look at this: https://github.com/ethereum/eth-account/blob/master/eth_account/signing.py

@pipermerriam
Copy link
Member

closing, but please continue to comment if you still need help.

@carver
Copy link
Collaborator

carver commented Mar 21, 2018

@Netherdrake
Copy link
Author

Actually, what I'm really looking for now is this for Python.

Basically something to validate client-side signed typed messages, such as those in metamask

@carver
Copy link
Collaborator

carver commented Apr 10, 2018

I guess metamask implemented some version of ethereum/EIPs#712 (perhaps at an older version than the current, which is still unmerged). There is no implementation of that yet AFAIK, but as soon as the draft EIP is merged, it seems reasonable to add it to eth-account & web3py.

@Netherdrake
Copy link
Author

Is there any python implementation of EIP712 at this time?

@carver
Copy link
Collaborator

carver commented Apr 10, 2018

Not that I've seen

@andrevmatos
Copy link

andrevmatos commented Apr 12, 2018

MicroRaiden has functions for current Metamask implementation. Look at its usage here.

@carver
Copy link
Collaborator

carver commented Apr 12, 2018

Thanks @andrevmatos -- so to sign a typed data message now, you can:

That version of eth-account will be included in the next web3.py release.


Here is a copy of the hashing code, in case that commit disappears for whatever reason:

def eth_sign_typed_data_message(typed_data: List[TypedData]) -> bytes:
    typed_data = [('{} {}'.format(type_, name), data) for type_, name, data in typed_data]
    schema, data = [list(zipped) for zipped in zip(*typed_data)]

return keccak256(keccak256(*schema), keccak256(*data))

@Netherdrake
Copy link
Author

Netherdrake commented Apr 12, 2018

I am unable to verify MetaMask signed message with either Account.recoverHash() or pruned version of Raiden's crypto.py.

from src.contrib.crypto import (
    eth_typed_data_message,
    addr_from_sig,
)
from eth_utils import decode_hex
from eth_account import Account

address = '0xaaf3ffee9d4c976aa8d0cb1bb84c3c90ee6e9118'
signature = '0x965439c02ec9399b9fce8f9eac57bfa7f93385c885c96c536dc28070bcdf0ccc6cc45c9eaf4f43b0a40c32e8f8d7e97320a92ea675fa5d826ae67521d3e669af1c'
msg = [[{"type":"string","name":"Video ID","value":"v3jMYxUpBzZ2"},{"type":"uint8","name":"Vote Weight (%)","value":100}],"0xaaf3ffee9d4c976aa8d0cb1bb84c3c90ee6e9118"]


msg_hash = eth_typed_data_message(msg[0])

print('Expected', address)
print('Raiden', Account().recoverHash(msg_hash, signature=signature))
print('eth-account', addr_from_sig(decode_hex(signature), msg_hash))

Output:

Expected 0xaaf3ffee9d4c976aa8d0cb1bb84c3c90ee6e9118
Raiden 0xb2EFAFf151948a284e56d653802ED8a579c964Ef
eth-account 0xb2efaff151948a284e56d653802ed8a579c964ef

Note:
This is how the message is signed in the first place:

let params = [
    [
        {type: 'string', name: 'Video ID', value: videoId},
        {type: 'uint8', name: 'Vote Weight (%)', value: weight}
    ],
    account,
];

w3.currentProvider.sendAsync({
    method: 'eth_signTypedData',
    params: params,
    from: account,
}).then(...);

As per: https://medium.com/metamask/scaling-web3-with-signtypeddata-91d6efc8b290

@carver
Copy link
Collaborator

carver commented Apr 12, 2018

I wouldn't be shocked if metamask's eth_signTypedData hashes differently than the eth_typed_data_message. I think the EIP proposal has gone through several revisions.

@Netherdrake
Copy link
Author

Netherdrake commented Apr 12, 2018

It looks like Metamask implementation uses a library with different packing method than the one provided by Raiden.

Does anyone know of a Python implementation of this:
https://github.com/ethereumjs/ethereumjs-abi/blob/09c3c48fd3bed143df7fa8f36f6f164205e23796/lib/index.js#L441

@Netherdrake
Copy link
Author

Ok, found the culprit. pack in crypto.py does not automatically infer int size from type name.

@Jmjin
Copy link

Jmjin commented Sep 18, 2018

@carver What is the current status for supporting eth_signTypedData in eth-account & web3py? Do you have a rough estimate when this will be available for use?

@Jmjin
Copy link

Jmjin commented Sep 18, 2018

@Netherdrake How did you deal with crypto.py not automatically inferring int size from type name?

@carver
Copy link
Collaborator

carver commented Sep 18, 2018

@Jmjin I'd like to see someone shepherd https://eips.ethereum.org/EIPS/eip-712 through to at least "Accepted" or "Last Call" before I work on adding it to eth-account/web3.

@Jmjin
Copy link

Jmjin commented Sep 19, 2018

@carver Actually it's already merged: ethereum/EIPs#712
Seems like https://eips.ethereum.org/EIPS/eip-712 is not up to date.

@carver
Copy link
Collaborator

carver commented Sep 19, 2018

@pipermerriam
Copy link
Member

This is captured in ethereum/eth-account#35 which is the codebase where this would be implemented.

@pipermerriam
Copy link
Member

Also, regarding: ethereum/eth-account#35 we are ready for it to be implemented if someone has capacity, but it will be heavily flagged as experimental until the EIP is finalized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants