[Ethereum] Recovered address not matching signer address (using ethers)

buidlerethers.jssignaturesolidity

Below is a simple signature verification flow I wrote, along with a test case with Buidler (all mainly to learn how to verify signatures in a Solidity smart contract). The problem is that the address I recover from the signature and the associated hash is different from the address of the signer that signed the hash. Moreover, (weirdly) the recovered address changes if the hash being signed is changed. I'm sure that shouldn't happen. I'd really appreciate if you could point out what I'm doing wrong!

The contract:

//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.0;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";
import "@nomiclabs/buidler/console.sol";

contract Verification {

    bytes32 public hash;
    bytes public signature;

    constructor(string memory toHash) public {
        bytes32 temp = keccak256(abi.encode(toHash));
        hash = ECDSA.toEthSignedMessageHash(temp);
    }

    function setSignature(bytes calldata _signature) external {
        signature = _signature;
    }

    function verify(address signer) external view returns(bool) {
        
        address recoveredAddr = ECDSA.recover(hash, signature);
        //console.log(recoveredAddr);
        return recoveredAddr == signer;
    }

}

The test script. The first test passes, the second one fails:

const { expect } = require("chai");
const { ethers } = require("@nomiclabs/buidler");

describe("Verification", function() {

    let verify;
    let signer;

    before(async () => {
        const Verification = await ethers.getContractFactory("Verification");
        verify = await Verification.deploy("Hash this message");
        await verify.deployed();

        const addresses = await ethers.getSigners();
        signer = addresses[1];
    })

    // Test objective: To check that signature generated here in the script is stored, as is, in the contract.
    it("Should verify that signature in the contract and the ethers script are identical", async function() {

        const _hash = await verify.hash();
        const _signature = await signer.signMessage(_hash);

        await verify.setSignature(_signature);

        expect(await verify.signature()).to.equal(_signature);
    });

    it("Should verify that the signer signed the hash the contract was deployed with.", async function() {

        const _hash = await verify.hash();
        const _signature = await signer.signMessage(_hash);

        await verify.setSignature(_signature);
        
        signerAddr = await signer.getAddress();
        //console.log(`Signer address: ${signerAddr}`);

        expect(await verify.verify(signerAddr)).to.equal(true);
    })
})

Appreciate the help. Thanks!

Best Answer

A note in ethers documentation seems to apply in your case:

A common case is to sign a hash. In this case, if the hash is a string, it must be converted to an array first, using the arrayify utility function.