Ethereumjs-Utils – How to Sign and Verify Data with Ethereumjs-Utils

ethereumjskeccaknodejssolidityweb3js

In my contract I'm verifying if some data was correctly signed by the owner. It looks like this:

require(
    owner() ==
        ecrecover(keccak256(abi.encodePacked(this, aCustomId)), v, r, s),
        "owner should sign aCustomId"
    );

Which means i should be retrieving v, r, s from a backend, which I currently do like this:

import * as ethUtil from 'ethereumjs-util';

const hash = soliditySha3(
  'myContractAddress',
  '11111',
);
const signature = ethUtil.ecsign(
    ethUtil.toBuffer(hash),
    wallet.getPrivateKey(),
);

return {
      r: ethUtil.bufferToHex(signature.r),
      s: ethUtil.bufferToHex(signature.s),
      v: signature.v,
      aCustomId: 11111,
};
// return the v, r, s

however, it seems like it's not correct as I keep getting "owner should sign aCustomId".

How can i create the correct hash so my v,r,s parameters are correct for recovering later ?

I'm using solidity version 0.6.12

The contract is called something like this:

myContract.methods
    .someMethod(
        11111,
        vValueAsInt,
        "sValue",
        "rValue",
    )
    .send({ from: account }, function(
        error: any,
        transactionHash: any
    ) {
        console.log(transactionHash);
        console.log(error);
    });

Best Answer

Here is a working code with web3js 1.3.0, ethereumjs-utils 5.2.5 and solidity 0.6.12 :

Javascript part

//hash the data
const hash = web3.utils.soliditySha3(
myContractAddress,
aCustomId,
);
//prefix the hash 
const prefixedHash = ethUtil.hashPersonalMessage(ethUtil.toBuffer(hash));

//get the ECDSA signature and its r,s,v parameters
const privateKey = new Buffer(myPrivateKey, "hex")
const signature = ethUtil.ecsign(prefixedHash, privateKey);
r = ethUtil.bufferToHex(signature.r),
s = ethUtil.bufferToHex(signature.s),
v = signature.v;

//call the contract method
myContract.methods.verifySig(accountAddress, aCustomId, r, s, v) 
.send({ from: accountAddress }).on('receipt', function(receipt){
  console.log(receipt);
})

Note that we use hashPersonalMessage in addition to soliditySha3 in order to add a prefix to the data before signing it. This prefix is : "\x19Ethereum Signed Message:\n32". It's a security standard which prevents attacks such as a malicious attempt to make a user sign a transaction while making him believe that he is signing a message.

Smart contract

function verifySig(address owner, uint256 aCustomId, bytes32 r, bytes32 s, uint8 v) public view {
bytes memory prefix = "\x19Ethereum Signed Message:\n32";
bytes32 hash = keccak256(abi.encodePacked(address(this), aCustomId));
bytes32 prefixedHash = keccak256(abi.encodePacked(prefix, hash));
require(ecrecover(prefixedHash, v, r, s) == owner);
}

The two important things here are :

  • To get the address of the contract we use address(this) (and not this).
  • We do the same operations that we did in the backend : first we hash the data, and then we compute the prefixed hash.
Related Topic