Basically on verifying the signature with the generated hash of the signed message, I'm getting the correct signer on JS, but not on Solidity.
JS Code (According to Keir Finlow-Bates' suggestion):
async function signHello() {
// Encode parameters
const encodedParams = ethers.utils.defaultAbiCoder.encode(
['string'],
['hello']
);
const hash = ethers.utils.hexlify(ethers.utils.toUtf8Bytes(encodedParams));
const prefixedHash = ethers.utils.solidityKeccak256(
['string', 'bytes'],
['\x19Ethereum Signed Message:\n' + hash.length, hash]
);
console.log("Hash: ", prefixedHash)
// Sign the message
const signature = await wallet.signMessage(ethers.utils.arrayify(prefixedHash));
console.log("Signature:", signature);
return {'hash': prefixedHash, 'signature': signature}
}
function getSigner(hash, signature) {
const digest = ethers.utils.keccak256(ethers.utils.solidityPack(['string', 'bytes32'], ["\x19Ethereum Signed Message:\n32", hash]));
return ethers.utils.recoverAddress(digest, signature);
}
signHello().then((res) => {
console.log(getSigner(res.hash, res.signature)) // getting the correct signer address here
})
JS Code:
const ethers = require('ethers');
const provider = new ethers.providers.JsonRpcProvider("https://polygon-mumbai.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY");
const privateKey = "YOUR_ACCOUNT_PRIVATE_KEY";
const wallet = new ethers.Wallet(privateKey, provider);
async function signHello() {
// Encode parameters
const encodedParams = ethers.utils.defaultAbiCoder.encode(
['string'],
['hello']
);
let hash = ethers.utils.keccak256(encodedParams);
console.log("Hash: ", hash)
// Sign the message
const signature = await wallet.signMessage(hash);
console.log("Signature:", signature);
return {'hash': hash, 'signature': signature}
}
function getSigner(hash, signature) {
return ethers.utils.verifyMessage(hash, signature)
// Using the code below, getting the same incorrect signer address as getting from the getSigner() solidity function
// const digest = ethers.utils.keccak256(ethers.utils.solidityPack(['string', 'bytes32'], ["\x19Ethereum Signed Message:\n32", hash]));
// return ethers.utils.recoverAddress(digest, signature);
}
signHello().then((res) => {
console.log(getSigner(res.hash, res.signature)) // getting the correct signer address here
})
Solidity Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract Signer {
function getHash() public pure returns (bytes32) {
return keccak256(
abi.encode(
"hello"
)
);
}
function getSigner(bytes32 _hash, bytes memory _signature)
public
pure
returns (address)
{
bytes32 r;
bytes32 s;
uint8 v;
if (_signature.length != 65) {
return address(0);
}
assembly {
r := mload(add(_signature, 32))
s := mload(add(_signature, 64))
v := byte(0, mload(add(_signature, 96)))
}
if (v < 27) {
v += 27;
}
if (v != 27 && v != 28) {
return address(0);
} else {
return
ecrecover(
keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
_hash
)
),
v,
r,
s
);
}
}
function getSignerUsingOpenzeppelin(bytes32 _hash, bytes memory _signature)
public
pure
returns (address)
{
return ECDSA.recover(_hash, _signature);
}
}
PS, I'm getting the same hash for hello in both JS as well as Solidity codes, i.e., 0x984002fcc0ca639f96622add24c2edd2fe72c65e71ca3faa243e091e0bc7cdab
.
Using the JS Code (According to Keir Finlow-Bates' suggestion), getting this hash:
0xd9f807e25c27377c0d87443b1736dfaa5c3a582d7023b696acf4dde098ee659e
Which is when used with the corresponding generated signature, returning the correct/expected signer.
But, now the question is how to generate the same hash in solidity?
As keccak256(abi.encode("hello"))
is returning a different hash i.e., 0x984002fcc0ca639f96622add24c2edd2fe72c65e71ca3faa243e091e0bc7cdab
.
Best Answer
The correct way to sign the message hash in JS is to
arrayify
it first, usingethers.utils.arrayify
, so the signHello() and getSigner() functions can be rewritten like this:So, now the generated hash is exactly identical in both JS and Solidity, also the signature can be verified in both, returning the correct/expected signer.