I am trying to sign a message in NodeJS and verify the signature with smart contract. Here is my code:

async function prepareAndSignMessage(to, amount, referenceTransaction){
    let contractInstance = new web3.eth.Contract(multisigABI, process.env.MULTISIG_CONTRACT_ADDRESS);
    //get keccak256 hash from contract
    let messageHash = await contractInstance.methods.getMessageHash(to, amount,  referenceTransaction).call();
    //sign message
    let signature = await web3.eth.accounts.sign(messageHash, process.env.ETHEREUM_PRIVATE_KEY);
    //  message: '0x9705f46c17b68492339627f99af8d148a9bf3a3075b89bb1a663bc26db530072',
    //  messageHash: '0x9ba3f82569283f1c3899ddec04c526b4f55d92d2ed6963497bbb09ee31bbd924', <-- This hash should match
    //  v: '0x1c',
    //  r: '...',
    //  s: '...',
    //  signature: '...'


And smart contract to recover & verify a signature:

function recover(address _to, uint256 _amount, string memory _reference) public returns(address){
    bytes32 hash = addPrefixAndHash(getMessageHash(_to, _amount, _referenece));
    //This hash should be same as one returned by web3. But it's different?????

function addPrefixAndHash(bytes32 _hash) public returns(bytes32) {
    return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _hash));

function getMessageHash(address _to, uint256 _amount, string memory _reference) public returns(bytes32) {
    return keccak256(abi.encodePacked(_to, _amount, _reference));

Hash from addPrefixAndHash should match hash returned by web3js web3.eth.accounts.sign function. But it's not.

web3.eth.accounts.sign is defined here: https://github.com/ChainSafe/web3.js/blob/1.x/packages/web3-eth-accounts/src/index.js#L302

Any ideas what's wrong? Thanks in advance.

  • Modifying web3 internal behavior doesn't look like a good idea. Web3 has web3.utils.soliditySha3 that behaves identical to solidity's keccak256.
    – Ismael
    Jan 15, 2021 at 12:08
  • Consider using Foundry and you will not bump into this problem in the first place because you will not be using JavaScrip anymore. Aug 24, 2022 at 11:00

1 Answer 1


I fixed the issue by modifying web3 function and removing the part that hashed the message (at https://github.com/ChainSafe/web3.js/blob/1.x/packages/web3-eth-accounts/src/index.js#L302)

Accounts.prototype.sign = function sign(data, privateKey) {
if (!privateKey.startsWith('0x')) {
    privateKey = '0x' + privateKey;

// 64 hex characters + hex-prefix
if (privateKey.length !== 66) {
    throw new Error("Private key must be 32 bytes long");

//var hash = this.hashMessage(data);
var hash = data; 
var signature = Account.sign(hash, privateKey);
var vrs = Account.decodeSignature(signature);
return {
    message: data,
    messageHash: hash,
    v: vrs[0],
    r: vrs[1],
    s: vrs[2],
    signature: signature

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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