0

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);
    }
}
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.