ECDSA.recover different from ethers.utils.verifyMessage

I'm not able to get the ECDSA library to recover a signature generated ethers.js. Aside from this I'm seeing inconsistencies from what the OZ docs say and how this library behaves.

Consider the following code example:

  1. Ethers sign -> Ethers recover (WORKING)
const wallet = this.owner
    const message = "Hello, world!"
    const messageHash = ethers.utils.solidityKeccak256(["string"], [message])
    const messageHashBinary = ethers.utils.arrayify(messageHash)
    const signature = await wallet.signMessage(messageHashBinary)
    const verified = ethers.utils.verifyMessage(messageHashBinary, signature)

    console.log("owner    ", this.owner.address)
    console.log("verified ", verified)

    expect(verified).to.eq(this.owner.address)

outputs:

owner     0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
verified  0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  1. Ethers sign -> ECDSA solidity recover (NOT WORKING)
const wallet = this.owner
    const message = "Hello, world!"
    const messageHash = ethers.utils.solidityKeccak256(["string"], [message])
    const messageHashBinary = ethers.utils.arrayify(messageHash)
    const signature = await wallet.signMessage(messageHashBinary)
    const recovered = await this.contract.recover(signature)

    console.log("owner     ", this.owner.address)
    console.log("recovered ", recovered)

    expect(recovered).to.eq(this.owner.address)
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.9;

import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "hardhat/console.sol";

contract Sanity {
  constructor() {}

  function recover(bytes calldata signature) public view returns (address) {
    bytes32 hash = keccak256(abi.encodePacked(msg.sender));
    bytes32 message = ECDSA.toEthSignedMessageHash(hash);
    address signer = ECDSA.recover(message, signature);

    console.log("check() recovered:  ", signer);

    return signer;
  }
}

output:

check() recovered:   0xe5b19adb9d20f771fd29cdd873440487edd53f74
owner      0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
recovered  0xe5b19adb9d20F771fD29Cdd873440487eDd53F74

I'm not seeing why this would return a different recovered address, or more importantly, why it doesn't return 0 for a failed recovered address, like the OZ documentation states:

This call does not revert if the signature is invalid, or if the signer is otherwise unable to be retrieved. In those scenarios, the zero address is returned

It's really confusing to have a signature recovery/verification operation return a non-zero result when given bad input.

How is this library supposed to be used in conjunction with ethers/web3 for signature verification? And why is it returning a non-zero value on a recovered bad message hash?

Hello @ianh

I'm sure you can spot the issue:

    const message = "Hello, world!"
    const messageHash = ethers.utils.solidityKeccak256(["string"], [message])
    const messageHashBinary = ethers.utils.arrayify(messageHash)

vs

    bytes32 hash = keccak256(abi.encodePacked(msg.sender));
    bytes32 message = ECDSA.toEthSignedMessageHash(hash);

Spoiler: your message are completely different, so of course the signature is not going to match.