Solidity – How to Recover Parameter from Signed Message

ethers.jskeccaksignaturesolidity

I got a problem with keccak hashing.

The Goal:

  1. A Server should create a parameter list and a sign. (I try it here with just one uint256 parameter)
  2. The Contract should check if the sign fits with the parameter list (Thats why I need to create the message in the contract again – it is not enough to check if the signer is correct for the given signiture)

… I do not get the correct hash from: "keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash)"

Test:

it("Test Sign", async function () {

accounts = await hre.ethers.getSigners();
deployer = accounts[1]

const SignTest = await ethers.getContractFactory("SignTest");
const iSignTest = await SignTest.connect(deployer).deploy();
await iSignTest.deployed();


const strInput = "Test"// 
console.log("test_String: ", strInput)

const amount = ethers.BigNumber.from("1")
console.log("test_amount: ", amount)



const message0 = utils.keccak256(
  utils.defaultAbiCoder.encode(["uint256"], [amount])
)

// const message1 = utils.keccak256(
//   utils.defaultAbiCoder.encode(["uint256","string"], [amount,strInput])//  
// )


const hash0 = ethers.utils.hashMessage(message0)
// const hash1 = ethers.utils.hashMessage(message1)


var sig0 = await deployer.signMessage(message0);
// var sig1 = await deployer.signMessage(message1);

console.log("test_message0: ", message0)
console.log("test_hash0: ", hash0)

// console.log("test_message0-type: ", typeof message0)
// console.log("test_message0-type: ", typeof message0)

// console.log("test_message0: ", message0)
// console.log("test_message1: ", message1)
// console.log("test_premessage0: ", hash0)
// console.log("test_premessage1: ", hash1)
// console.log("test_sig0: ", sig0)
// console.log("test_sig1: ", sig1)


var toProve = await iSignTest.test(amount, strInput, message0, sig0, hash0)
console.log("Signer: ", deployer.address)

const breakdown = utils.splitSignature(sig0)
// console.log("Sig breakdown: ", utils.splitSignature(sig))



});

Contract:

contract SignTest {

// using ECDSA for bytes32;

address owner = msg.sender;

mapping(uint256 => bool) usedNonces;

function test(uint256 amount, string memory inpStr, bytes32 checkMsg, bytes memory sig0, bytes32 inpHash) public returns(address) {
    
    bytes32 msg0 = keccak256(abi.encodePacked(amount));

    console.log("msg");
    console.logBytes32(msg0);

    require(msg0 == checkMsg, "Wrong msg0");

    bytes32 prefixed = perfixTest(msg0);
    require(inpHash == prefixed, "Wrong prefix");

    address signer = recoverSigner(msg0,sig0);
    console.log("Signer: ", signer);

    return address(0);

}   

function perfixTest(bytes32 inputHash) internal view returns(bytes32) {
    bytes32 shouldBe = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));

    bytes32 t0 = keccak256(abi.encodePacked("\x19Ethereum Signed Message:", inputHash));
    bytes32 t1 = keccak256(abi.encodePacked("\x19Ethereum Signed Message:32", inputHash));
    bytes32 t2 = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));
    bytes32 t3 = keccak256(abi.encodePacked("\x19Ethereum Signed Message:32\n", inputHash));
    bytes32 t4 = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n", inputHash));

    bytes32 t5 = keccak256(abi.encodePacked("\x19Ethereum Signed Message", inputHash));
    bytes32 t6 = keccak256(abi.encodePacked("\x19Ethereum Signed Message32", inputHash));
    bytes32 t7 = keccak256(abi.encodePacked("\x19Ethereum Signed Message\n32", inputHash));
    bytes32 t8 = keccak256(abi.encodePacked("\x19Ethereum Signed Message32\n", inputHash));
    bytes32 t9 = keccak256(abi.encodePacked("\x19Ethereum Signed Message\n", inputHash));

    bytes32 t10 = keccak256(abi.encodePacked("\x19", inputHash));

    console.log("shouldBe");
    console.logBytes32(shouldBe);

    console.log("t0");
    console.logBytes32(t0);

    console.log("t1");
    console.logBytes32(t1);

    console.log("t2");
    console.logBytes32(t2);

    console.log("t3");
    console.logBytes32(t3);

    console.log("t4");
    console.logBytes32(t4);

    console.log("t5");
    console.logBytes32(t5);

    console.log("t6");
    console.logBytes32(t6);

    console.log("t7");
    console.logBytes32(t7);

    console.log("t8");
    console.logBytes32(t8);

    console.log("t9");
    console.logBytes32(t9);

    console.log("t10");
    console.logBytes32(t10);

    return shouldBe;

}
...
}

The Output:

SignTest
test_String:  Test
test_amount:  BigNumber { value: "1" }
test_message0:  0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
test_hash0:  0x83e1e609f6e437e4b1b0b8f55f889d630f6e370c7822706b6f01b2021b80023e
msg
0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
shouldBe
0x3774a77bfb45a273febe88cfa6a2e064f33bda3d273ffd1789c83de39d861cda
t0
0x824943dde35e6711106f370b125e24dd0575b7515156591fe2826580dadfb545
t1
0x6010d0d77e4ef38b121f73450f88d9ead35733c6466906167d9db82fd47a5a1c
t2
0x3774a77bfb45a273febe88cfa6a2e064f33bda3d273ffd1789c83de39d861cda
t3
0x8636027d0098a1d8bffb9850456a6843561d8f999eb32b6504a06e43de270826
t4
0xe4a070576f45c577b0bc5bfba5cc0f7a5e58eefb087b5ff0ca8573443f95d4ef
t5
0x96d81a91ffdf8855859535927e389489daea3b0d1a4ad194aa8bc5f48ed05570
t6
0x6f032e9e6e063263446d4850fc00d5181dadbd2ed827d7a0555b8828e52f2766
t7
0x3c1c58823ced9e929caecc6af5926d61f1cf399c55b92d4a144dfad849a67a12
t8
0x040ebab1408207f4ab4c4a17f93314e4173f2e7ceac8ec3e5817f4ec1fbc8b9a
t9
0xb6d16314cd28df003f74eb15434c464faaf56a631011e69de3b0d228f9575784
t10
0x92260e8161838b56f23e51ce7147f3b76f4865d18708f7c0dc0dd6e6f000f96b
    1) Test Sign


  0 passing (4s)
  1 failing

  1) SignTest
       Test Sign:
     Error: VM Exception while processing transaction: reverted with reason string 'Wrong prefix'...

In my world… the way to set the "shouldBe" variable should be the same hash as the output of "test_hash0" … But it is not …

Hope someone can help.

Best Answer

Just found out by myself... Here the answer if someone stuck at the same problem:

ethers.utils.hashMessage() 

Can take a string or binary datas... from Doc(https://docs.ethers.io/v5/api/utils/hashing/):

NOTE: It is important to understand how strings and binary data is handled differently. A string is ALWAYS processed as the bytes of the string, so a hexstring MUST be converted to an ArrayLike object first.

So ethers can use a string and solidtiy (as far as I unserstand) use a bytes32 array.

utils.keccak() returns a string. So I had to cast that string to an bytes (bytes 32 in my case) with:

const messageHashBinary = ethers.utils.arrayify(message0);
const hash0 = ethers.utils.hashMessage(messageHashBinary);

In that way I get the same hash in solidity an js.