Possible error in ECDSA.sol
See original GitHub issueIt appears that ECDSA.recover returns incorrect address for a signature created on the client side by ethers.SignerWithAddress.signMessage.
💻 Environment
Solidity 8.4 ethers.js version 5.4.7 Hardhat version 2.6.5 Running in VS Code locally with MetaMask 10.2.2
📝 Details
I am attempting to use ECDSA.recover to confirm that that a document string supplied to contract in a function call was in fact signed by the account of msg.sender. I have been unable to find any error in the code below but perhaps I misusing the libraries somehow.
It may the problem is that the signature produced by ethers.SignerWithAddress.signMessage(msg)
isn’t the kind of signature ECDSA is meant to work with, but I couldn’t tell from the documentation.
I noted that the actual signature produced is a string 132 characters in length, but ECDSA.recover expects a signature of 65 bytes. I noticed that apply abi.encodePacked
to the signature reduces it length from 132 to 65, so I assume that is what you’re supposed to do, but perhaps not since the function returns an address different than the address of the account singing the message.
🔢 Code to reproduce bug
Client side code (Typescript):
let contract_address = await TokenHelper.deploy();
let hm = await ethers.getContractAt("HiveMind", contract_address)
let message : string = "check that this message was signed by account0";
//hash the plain text message;
let messageHash = keccak256(utils.toUtf8Bytes(message));
//account0 signs the message;
let signature = await address0.signMessage(utils.toUtf8Bytes(messageHash));
//verify the message
let output = await hm.connect(address0.address).verifyMessage(message, signature);
console.log(`Actual address: ${address0.address}`)
console.log(`Recovered address: ${output[0]} ${output[1]}`);
console.log("");
console.log("ethers.js version", ethers.version)
Output
TokenTest
Actual address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Recovered address: 0xB21D540039aB6A0F01b01c0b7bccdC887B67Bc56 false
ethers.js version ethers/5.4.7
Contract Code:
function verifyMessage(string memory message, bytes memory signature)
public view returns(address, bool) {
//hash the plain text message
bytes32 messagehash = keccak256(bytes(message));
bytes memory sig = abi.encodePacked(signature);
address signeraddress = messagehash.toEthSignedMessageHash().recover(sig);
if (msg.sender==signeraddress) {
//The message is authentic
return (signeraddress, true);
} else {
//msg.sender didnt sign this message.
return (signeraddress, false);
}
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (5 by maintainers)
Ok, @julianmrodri had the answer but it wasn’t
solidityKeccak256
. 😄@grapevinegizmos The problem is that you’re signing
utils.toUtf8Bytes(messageHash)
, i.e. the string corresponding to the hex encoded message hash, as opposed to the actual bytes of the message hash.The fix is to use
arrayify
:I’ve confirmed that this recovers to the correct address.
Many thanks @frangio @julianmrodri
I was able to get it working. If either of you are in a position to update the documentation on OpenZeppelin of how to actually use ECDSA I think it might save developers a lot of time to see an example like this of how to encode a message on the client side so that you can decode the signature within the contract. Its not so obvious, as maybe you can see. Thanks again.