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

[Question] how to verify a metamask signed message? #564

Closed
chriamue opened this issue Oct 30, 2021 · 5 comments
Closed

[Question] how to verify a metamask signed message? #564

chriamue opened this issue Oct 30, 2021 · 5 comments

Comments

@chriamue
Copy link

Hi I fail verifying a signed message.
When signing a message with metamask we get something like:

{
  "address": "0x63F9A92D8D61b48a9fFF8d58080425A3012d05C8",
  "msg": "0x63f9a92d8d61b48a9fff8d58080425a3012d05c82kl75awln2l",
  "sig": "0x7097b9a0810d13e60182f261708c2c260e86ea09853cd48b184a5a4b4ea08c02087bd86b8cdee3aec88ac365ec533c9a111e5b5586c4941a790ca46970c3f3801c",
  "version": "2"
}

This can be validated here: https://app.mycrypto.com/sign-message

Now I try to validate this signature in a rust written backend, with no luck.

Here is the simplified test code:

use web3::signing::{keccak256, recover};

pub fn validate(account: String, nonce: String, signature: String, chain: Option<i32>) -> bool {
    let message_hash = keccak256(format!("{}{}", account, nonce).as_bytes());
    let signature = &signature[2..];
    let signature_hash = keccak256(signature.as_bytes());
    let pubkey = recover(&message_hash, &signature_hash, chain.unwrap_or(42));
    println!("{} {:?} {:?}", account, message_hash, pubkey);
    true
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate() {
        let account = "0x63f9a92d8d61b48a9fff8d58080425a3012d05c8".to_string();
        let nonce = "2kl75awln2l".to_string();
        // let message = "0x63f9a92d8d61b48a9fff8d58080425a3012d05c82kl75awln2l".to_string();
        let signature = "0x7097b9a0810d13e60182f261708c2c260e86ea09853cd48b184a5a4b4ea08c02087bd86b8cdee3aec88ac365ec533c9a111e5b5586c4941a790ca46970c3f3801c".to_string();

        assert!(validate(account, nonce, signature, Some(2)));
    }
}

Before I tried hashing the message, I got InvalidMessage.

Two questions come into my mind:

(1) Now I get InvalidSignature, so I think I make something wrong converting the signature.
(2) Where do I get the recover_id from?

Kind regards,
Christian

@tomusdrw
Copy link
Owner

You shouldn't be hashing the signature. Also you should first hex-decode the signature instead of using the string. The signature is expected to be 65 bytes (r ++ v ++ s) byte blob. The recovery id is v value.

Please take a look at https://docs.rs/ethsign/0.8.0/ethsign/struct.Signature.html as well.

@chriamue
Copy link
Author

Thank you for the hints.
I could not really find out which way it is best to calculate the recover id, maybe I have to take modular of 2,
but this works for me now.

    use web3::signing::{keccak256, recover};

    pub fn eth_message(message: String) -> [u8; 32] {
        keccak256(
            format!(
                "{}{}{}",
                "\x19Ethereum Signed Message:\n",
                message.len(),
                message
            )
            .as_bytes(),
        )
    }

    #[test]
    fn test_recover() {
        let account = "0x63f9a92d8d61b48a9fff8d58080425a3012d05c8".to_string();
        let message = "0x63f9a92d8d61b48a9fff8d58080425a3012d05c8igwyk4r1o7o".to_string();
        let message = eth_message(message);
        let signature = hex::decode("382a3e04daf88f322730f6a2972475fc5646ea8c4a7f3b5e83a90b10ba08a7364cd2f55348f2b6d210fbed7fc485abf19ecb2f3967e410d6349dd7dd1d4487751b").unwrap();
        println!("{} {:?} {:?}", account, message, signature);
        let pubkey = recover(&message, &signature[..64], 0);
        assert!(pubkey.is_ok());
        let pubkey = pubkey.unwrap();
        let pubkey = format!("{:02X?}", pubkey);
        assert_eq!(account, pubkey)
    }

A very important part is the salted message which is signed by metamask.

Thanks,
Christian

@tomusdrw
Copy link
Owner

tomusdrw commented Nov 1, 2021

The v component might also contain replay protection (chain_id), so before getting the actual recovery value (0/1) you need to remove the chain_id and normalize it. See more here: ethereum/EIPs#155

@mzxyz
Copy link

mzxyz commented May 4, 2022

For anyone who have this issue, as for Ethereum the v value is [27|28], to calculate the actual recovery_id will make recover working:

let recovery_id = signature[64] as i32 - 27;
let pubkey = recover(&message, &signature[..64], recovery_id);

@sdelvalle57
Copy link

For anyone who have this issue, as for Ethereum the v value is [27|28], to calculate the actual recovery_id will make recover working:

let recovery_id = signature[64] as i32 - 27;
let pubkey = recover(&message, &signature[..64], recovery_id);

This did the trick, this is the whole code for anyone needing it:

pub fn eth_message(message: String) -> [u8; 32] {
    keccak256(
        format!(
            "{}{}{}",
            "\x19Ethereum Signed Message:\n",
            message.len(),
            message
        )
        .as_bytes(),
    )
}


#[test]
fn test_recover3() {
    let account = "0x23C6599aAdF44Be7cbaD6D9051bb4C2255b2f713".to_string();
    let message = "heelo".to_string();
    let message = eth_message(message);
    let signature = hex::decode("7dabc0471d53ed34ec21c0257d8b40f7234d2bce8ccdbfba540a8f2be56183a954e49162be4768c14efeb6ad7f1a836d2a3385df628ede34f7047af65f18cc621c").unwrap();
    println!("{} {:?} {:?}", account, message, signature);
    let recovery_id = signature[64] as i32 - 27;
    let pubkey = recover(&message, &signature[..64], recovery_id);
    assert!(pubkey.is_ok());
    let pubkey = pubkey.unwrap();
    let pubkey = format!("{:02X?}", pubkey);
    assert_eq!(account.to_lowercase(), pubkey.to_lowercase())
}

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

4 participants