Unable to verify signature

metamasksignaturesolidityverifyweb3js

I'm trying to get the hang of Web3 and Solidity for an upcoming project. So for now I am just fiddling around with some different things, and I have gotten myself stuck on verifying signatures. I am able to sign, but I cannot verify. I have been trying for 3-4 hours every night for the past two nights now, but I am simply unable to figure out what the issue is, so now I come crawling here for help.

I have included both my clientside and contract code here. I hope someone can point me in the right direction. I do get a result back from the call to verify on the contract, but the result is always false.

The contract code is more or less copy/pasted from https://solidity-by-example.org/signature/, which I am aware is a bit outdated.

All addresses and keys in the example below are from testnet.

Clientside, using MetaMask:

const contractAddress = '0x94F9f9d16f2cb8542BE31D6fA99471Ea8251D7eC';

const sign = async (address) => {
  const nonce = Math.floor(Math.random() * 10000000000) + 1;
  console.log(nonce);

  const data = [{"address": address}, {"uint256": 1000000}, {"uint256": nonce}, {"address": contractAddress}];
  console.log(window.web3.eth.accounts.sign(generateHash(data), '61323f1a2911e70573be49819e68a7dbffa10216135bb06171e596a4f89bfd79'));
}

const verify = async (address, nonce, signature) => {
  const contractInstance = new window.web3.eth.Contract(
    ABI[0].abi,
    contractAddress,
    {
      from: address,
    }
  );

  const result = contractInstance.methods.verify(contractAddress, address, 1000000, nonce, signature).call(function (error, result) {
    console.log(result);
  });
}

const generateHash = (data) => {
  const keys = data.map((item) => {
    return Object.keys(item)[0];
  });

  const values = data.map((item) => {
    return Object.values(item)[0];
  });

  return "0x" + ethereumjs.soliditySHA3(keys, values).toString("hex");
}

//sign('0x93E16885EE732BFF281285efe2f2F46Dc92590C5');
verify('0x93E16885EE732BFF281285efe2f2F46Dc92590C5', 3421724946, '0x7eba658b197add3604c7d25c9305507cc67dd7a9298862fb4fcf2a2bcfd274d6039681008fa7a6201194258dee17eed4d709ea704427135cb80731a44908d29a1b');
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import '@openzeppelin/contracts/access/Ownable.sol';

contract Moonboi is ERC20, Ownable {
  constructor() ERC20("Moonboi Token", "MBT") {}

    function getMessageHash(
        address _to, uint _amount, uint _nonce
    ) public view returns (bytes32) {
        return keccak256(abi.encodePacked(_to, _amount, _nonce));
    }

    function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) {
        return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash));
    }

    function verify(
        address _signer,
        address _to, uint256 _amount, uint256 _nonce,
        bytes memory signature
    ) public view returns (bool) {
        bytes32 messageHash = getMessageHash(_to, _amount, _nonce);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == _signer;
    }

    function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
        require(sig.length == 65, "invalid signature length");

        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }

        return (r, s, v);
    }
}

Best Answer

I figured out what I was doing wrong, so I will add my findings here as an answer, in case someone else has made the same mistake. The problem is pretty obvious once I realized what was going on.

When calling the smart contract method verify I was passing in the smart contract address as the signer, when in reality the signer is the owner of the smart contract.

So in this method:

const verify = async (address, nonce, signature) => {
  const contractInstance = new window.web3.eth.Contract(
    ABI[0].abi,
    contractAddress,
    {
      from: address,
    }
  );

  contractInstance.methods.verify(contractAddress, address, 1000000, nonce, signature).call(function (error, result) {
    console.log(error);
    console.log(result);
  });
}

.. the first parameter in the verify method should instead be the contract owner, like so:

const contractOwner = '0xa914E79148447A9affe1bCC4A72859e26c0f75AB';

contractInstance.methods.verify(contractOwner, address, 1000000, nonce, signature).call(function (error, result) {
  console.log(error);
  console.log(result);
});

Another mistake I made was that the private key needs to be prefixed with 0x, so:

window.web3.eth.accounts.sign(generateHash(data), '61323f1a2911e70573be49819e68a7dbffa10216135bb06171e596a4f89bfd79')

would not have worked. I should pass in 0x61323f1a2911e70573be49819e68a7dbffa10216135bb06171e596a4f89bfd79 instead.

Related Topic