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.

SYED ASAD KAZMI
  • 777
  • 2
  • 16
  • why are you implementing it by yourself? is it no better to use Openzeppelin contracts for this https://docs.openzeppelin.com/contracts/5.x/api/utils#ECDSA – Majd TL Mar 01 '24 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);

    – SYED ASAD KAZMI Mar 02 '24 at 08: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. – SYED ASAD KAZMI Mar 02 '24 at 09:07
  • https://ethereum.stackexchange.com/questions/12621/getting-the-wrong-address-back-from-ecrecover/12684

    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.

    – Keir Finlow-Bates Mar 03 '24 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 ? – SYED ASAD KAZMI Mar 03 '24 at 17:44
  • ethers library appends "\x19Ethereum Signed Message:\n32" prefix when ethers.utils.signMessage() is called, so while hash generation you can exclude that. – Nikhil Mar 03 '24 at 18:24
  • @Nikhil, thanks for responding. After excluding that, I'm getting a different hash i.e., 0xbd4e3e4488312fbdbebf9d859581be67532efbb0152c94e698f9829aa83b4a3f, but still how to get the same hash using solidity, that's where I'm stuck now. – SYED ASAD KAZMI Mar 03 '24 at 19:02

1 Answers1

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.

SYED ASAD KAZMI
  • 777
  • 2
  • 16