5

I need to be able to retrive 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;
        }
    }

Regretably, 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.

Would appreciate any pointers.

1 Answer 1

14

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);
        }
    }
  

    
3
  • omg, that "ethers.utils.arrayify(messageHash)" was the trick for me ':) Oct 26, 2021 at 14:33
  • Bless you mate ! I did the same mistake as you. These hashings and encodings are a bit confusing ...
    – gneric
    Feb 3, 2022 at 20:48
  • Thank you! This is esp. annoying as ethers.js docs are seriously lacking in this regard Sep 19 at 22:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.