GGizmos
GGizmos

Reputation: 3773

Can't validate authenticated message with ECDSA.recover

I need to be able to retrieve a document from the contract, have it signed by a users wallet and then send back to the contract and the signature validated.

Here's how I have address0 sign it on the client side:

   let message : string = "check this message signed by account0";
   let messageHash = keccak256(utils.toUtf8Bytes(message));
   let signature = await address0.signMessage(messageHash);
   await hm.connect(address0).verifyMessage(message, signature);

Here's the validator in my contract:

   function verifyMessage(string memory message, 
      bytes memory signature) 
      public view  returns(bool) {
      
        //hash the plain text message
        bytes32 messagehash =  keccak256(bytes(message));
        //hash the prefix and messagehash together   
        bytes32 messagehash2 = keccak256(abi.encodePacked("\x19Ethereum Signed Messsage:\n32", messagehash));
        //extract the signing contract address
        address signeraddress = ECDSA.recover( messagehash2, signature);
        if (msg.sender==signeraddress) {
            //The message is authentic
            return true;
        } else {
            //msg.sender didnt sign this message.
            return false;
        }
    }

Regrettably, the value returned by ECDSA.recover for signeraddress is not account0's address, and despite a lot of experimentation I've not been able to derive the correct address of the message sender from the signature.

Would appreciate any pointers.

Upvotes: 1

Views: 1624

Answers (1)

GGizmos
GGizmos

Reputation: 3773

I was able to get an answer from folks over on Openzeppelin.

In case any body else runs into the situation, one issue is the way that the hash is calculated and the signature is computed on the client side

Instead of

   let messageHash = keccak256(utils.toUtf8Bytes(message));

use

    let messageHash = ethers.utils.solidityKeccak256(['string'], [message]);

and instead of

    let signature = await address0.signMessage(messageHash);

use

     let signature = await address0.signMessage(ethers.utils.arrayify(messageHash));

On the server side, the addition of the prefix can be done more simply with `ECDSA.toEthSignedMessageHash() as in the solution below:

using ECDSA for bytes32; 

function verifyMessage(string memory message, bytes memory signature) public view  returns(address, bool) {
        //hash the plain text message
        bytes32 messagehash =  keccak256(bytes(message));
       
        address signeraddress = messagehash.toEthSignedMessageHash().recover(signature);
              
        if (msg.sender==signeraddress) {
            //The message is authentic
            return (signeraddress, true);
        } else {
            //msg.sender didnt sign this message.
            return (signeraddress, false);
        }
    }
  

    

Upvotes: 5

Related Questions