elliptic-php icon indicating copy to clipboard operation
elliptic-php copied to clipboard

Big Issue when recovering a signature of a hashed message

Open moda20 opened this issue 3 years ago • 3 comments

I am trying to verify the signature of a hashed message, and the method used in the description doesn't return the right address :


function verifySignature($message, $signature, $address) {
        $msglen = strlen($message);
        $hash   = Keccak::hash("\x19Ethereum Signed Message:\n{$msglen}{$message}", 256);
        $sign   = ["r" => substr($signature, 2, 64),
            "s" => substr($signature, 66, 64)];
        $recid  = ord(hex2bin(substr($signature, 130, 2))) - 27;
        if ($recid != ($recid & 1))
            return false;

        $ec = new EC('secp256k1');
        $pubkey = $ec->recoverPubKey($hash, $sign, $recid);
        return $address == $this->pubKeyToAddress($pubkey);
    }


$address   = "0xd927a97442c8bce9f18e84de11cac6e54a890ff8";
            $message   = "0xa880c297e04a9a4e1b8856dd4b48c1f6c0b0b82b1da2907b3d16f6ab1357c8b9";
// signature returned by eth.sign(address, message)
            $signature = "0xcd33577b169a3f2a5c835b3ca7dab1d41fa32db4b791c6856319756e7fecc3cb13676706408b019b6dcc3fe28a72f8435390bb0a1572ba241cfd09ae917784511c";

            if ($this->verifySignature($message, $signature, $address)) {
                Log::error("SUCCSS");
            } else {
                Log::error("FAIL");
            }

the address returned by verifySignature (that we try to compare to the original address) is 0xad21644cb255d77dbf4b1ab716cca9797ce3e5bb which is different than the original address.

The problem here is that when not signing the hashed message but the original message it works correctly.

the original message is : "It'sMe MArio". (without the quotes) and the hashing is done by sha3 : web3.utils.sha3(message)

moda20 avatar Aug 25 '22 08:08 moda20

I am trying to verify the signature of a hashed message, and the method used in the description doesn't return the right address :

function verifySignature($message, $signature, $address) {
        $msglen = strlen($message);
        $hash   = Keccak::hash("\x19Ethereum Signed Message:\n{$msglen}{$message}", 256);
        $sign   = ["r" => substr($signature, 2, 64),
            "s" => substr($signature, 66, 64)];
        $recid  = ord(hex2bin(substr($signature, 130, 2))) - 27;
        if ($recid != ($recid & 1))
            return false;

        $ec = new EC('secp256k1');
        $pubkey = $ec->recoverPubKey($hash, $sign, $recid);
        return $address == $this->pubKeyToAddress($pubkey);
    }


$address   = "0xd927a97442c8bce9f18e84de11cac6e54a890ff8";
            $message   = "0xa880c297e04a9a4e1b8856dd4b48c1f6c0b0b82b1da2907b3d16f6ab1357c8b9";
// signature returned by eth.sign(address, message)
            $signature = "0xcd33577b169a3f2a5c835b3ca7dab1d41fa32db4b791c6856319756e7fecc3cb13676706408b019b6dcc3fe28a72f8435390bb0a1572ba241cfd09ae917784511c";

            if ($this->verifySignature($message, $signature, $address)) {
                Log::error("SUCCSS");
            } else {
                Log::error("FAIL");
            }

the address returned by verifySignature (that we try to compare to the original address) is 0xad21644cb255d77dbf4b1ab716cca9797ce3e5bb which is different than the original address.

The problem here is that when not signing the hashed message but the original message it works correctly.

the original message is : "It'sMe MArio". (without the quotes) and the hashing is done by sha3 : web3.utils.sha3(message)

Quick fix is in js code and not in php end if finally got it working here is my script:

    //Load account from metamask
    const accounts = await window.ethereum.request({
        method: 'eth_requestAccounts'
    });
    console.log(accounts[0]);
    //prepare message to sign
    const msgtext = "Hello World";
    const hashedMessage = Web3.utils.fromUtf8(msgtext);
    console.log(hashedMessage);
    //get user to sign the message
    const signature = await window.web3.eth.sign(hashedMessage, accounts[0]);

Explaination: Thing is you have to take a string data and covert it to hex which returns a bytes32 hexcode and then ask user to sign it.

sumitkumar33 avatar Oct 22 '22 16:10 sumitkumar33

Explaination: Thing is you have to take a string data and covert it to hex which returns a bytes32 hexcode and then ask user to sign it.

I'm getting the same issue as soon as the provider is not MetaMask (e.g. WalletConnect)

With

        let publicAddress = address;
        let paToLower = publicAddress.toLowerCase();
        const signature = await web3ModalProv.eth.sign(web3ModalProv.utils.fromUtf8(message), paToLower);

or without ``` let message = response.data; let publicAddress = address; handleSignMessage(message, publicAddress).then(handleAuthenticate);

  function handleSignMessage(rawMessage, publicAddress) {
    let message = web3ModalProv.utils.utf8ToHex(rawMessage);

    return new Promise((resolve, reject) =>  
    web3ModalProv.eth.personal.sign(
      message,
      publicAddress,
        (err, signature) => {
          if (err || typeof signature === 'undefined') {
            userLoginData.state = "loggedOut";
            showMsg(userLoginData.state);
          }

          return resolve({ publicAddress, signature });
        }
      )
    )
  }
 ```

In all cases the PHP code returns false if it is not a MetaMask address at if ($recid != ($recid & 1)) If I use the same code with MetaMask, it works.

I'm using a fork of this https://github.com/giekaton/php-metamask-user-login to establish a login with WalletConnect

smitedd avatar Oct 26 '22 20:10 smitedd

I managed to fix the issue which was actually on the back-end (verifySignature).

Solution:

Include php-ecrecover in your project

This needs the following version of CryptoCurrencyPHP (I did not tested the HEAD one)

Simply replace function verifySignature() by:

function verifySignature($message, $signature, $address) 
  {
    return $address == personal_ecRecover($message, $signature);
  }

The personal_ecRecover function is based on gmp and looks more efficient than the current public key recovery method which seems to have been developed only for MetaMask.

You might need to allow gmp extension in php.ini if not done

smitedd avatar Oct 26 '22 23:10 smitedd