simplito / elliptic-php

Fast, general Elliptic Curve Cryptography library. Supports curves used in Bitcoin, Ethereum and other cryptocurrencies (secp256k1, ed25519, ..)
MIT License
220 stars 52 forks source link

Big Issue when recovering a signature of a hashed message #42

Open moda20 opened 2 years ago

moda20 commented 2 years ago

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)

sumitkumar33 commented 2 years ago

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.

gniax commented 2 years ago

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

gniax commented 2 years ago

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_ecRecoverfunction 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