1

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.

7
  • why are you implementing it by yourself? is it no better to use Openzeppelin contracts for this docs.openzeppelin.com/contracts/5.x/api/utils#ECDSA Commented Mar 1, 2024 at 17:41
  • @MajdT, I'm getting the incorrect signer even when using the Openzeppelin's ECDSA library, like this: return ECDSA.recover(_hash, _signature); Commented Mar 2, 2024 at 8:57
  • @MajdTL, Thanks for responding. I've added the function in the contract to get the signer using Openzeppelin's library too. It's returning a different signer, but still not the correct/expected one. Commented Mar 2, 2024 at 9:07
  • ethereum.stackexchange.com/questions/12621/… Usually people complain they're not recovering the right signature because they're not adding the signed message prefix. You either have to do it explicitly, or use both signing and recovery functions that do it for you. I suspect you're mixing them up here. Commented Mar 3, 2024 at 15:13
  • Thanks a lot, @KeirFinlow-Bates. I've edited the JS code by adding the signed message prefix as per your suggestion. But now, the generated hash is different from what's being generated in solidity, so how to generate the same hash in solidity as well ? Commented Mar 3, 2024 at 17:44

1 Answer 1

0

The correct way to sign the message hash in JS is to arrayify it first, using ethers.utils.arrayify, so the signHello() and getSigner() functions can be rewritten like this:

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 hash after arrayifying const signature = await wallet.signMessage(ethers.utils.arrayify(hash)); console.log("Signature:", signature); return {'hash': hash, 'signature': signature} } function getSigner(hash, signature) { // Verify the arrayifyed message hash with the signature to get the signer return ethers.utils.verifyMessage(ethers.utils.arrayify(hash), signature) } 

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.