I am using web3 library to sign a transaction and then verify it from the smart contract. The signature is not verifying.

This is the frontend code

const DomainSeparator = ethers.keccak256(
          ["string", "address"],
          ["0x01", contractAddress]
      const message = ethers.keccak256(
          ["address", "address", "uint256", "uint256"],
            1000, // amount
            0, // nonce
      let finalHash = ethers.keccak256(
          ["bytes1", "bytes1", "bytes32", "bytes32"],
          ["0x19", "0x01", DomainSeparator, message]

**This is the solidity smart contract code**

function transferWithPermit(address from, address to,uint256 amount,bytes memory signature) external {
        bytes32 message =  keccak256(abi.encode(from,to,amount,nonce[from]));
        (uint8 v, bytes32 r, bytes32 s) = extractRSV(signature);
        _validate(v, r, s, message, from);
function _validate(
        uint8 v,
        bytes32 r,
        bytes32 s,
        bytes32 encodeData,
        address signer
    ) internal view {
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", getDomainSeparator(), encodeData));
        address recoveredAddress = ecrecover(digest, v, r, s);
        // Explicitly disallow authorizations for address(0) as ecrecover returns address(0) on malformed messages
        console.log("recovered,  signer :",recoveredAddress, signer);
            require(recoveredAddress!= address(0) && (recoveredAddress == signer), "INVALID_SIGNATURE");

**This is my hardhat test case**

const provider = waffle.provider;
const web3 = require("web3");
const { ecsign } = require("ethereumjs-util");
describe('signature', () =>{
    const [owner, accountOne, feeToSetter] = provider.getWallets();
let zerotoken;
    before( async () =>{
const Zerotoken = await ethers.getContractFactory('ZeroToken');
zerotoken = await Zerotoken.deploy();
console.log("ZeroToken address: ",zerotoken.address)}) //same as the contractAddress on the //frontEnd
it('Digital Signature', async () =>{
    let nonce =0;
let tokenAmount=1000
let toAddress = '0xd82b09990f96EDBd6e7731C67e7ea4c9b01AF150';
    const DomainSeparator = ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode(["string", "address"], ["0x01", toAddress]));
var message =  ethers.utils.keccak256(ethers.utils.defaultAbiCoder.encode([ "address", "address", "uint256", "uint256"],[owner.address,toAddress,tokenAmount,nonce]));
var finalHash = ethers.utils.keccak256(
      ethers.utils.solidityPack(["bytes1", "bytes1", "bytes32", "bytes32"], ["0x19", "0x01", DomainSeparator, message]),
      console.log("final Hash: ",finalHash)
      const { v, r, s } = ecsign(Buffer.from(finalHash.slice(2), "hex"), Buffer.from(owner.privateKey.slice(2), "hex"));
  console.log('response', v.toString(16), r.toString('hex'), s.toString('hex'))
const signature = `0x${v.toString(16)}${r.toString('hex')}${s.toString('hex')}`
let transferWithPermit = await zerotoken.transferWithPermit(owner.address,toAddress,tokenAmount,signature)

First we deployed the contract. We called "transferWithPermit" function which in turn calls "validate" function. The owner address in the test case is same as the signer address on the frontend. Inside "_validate" function the recovered address is not same as the signer address from the frontend.

Message Hammad Ali

1 Answer 1


In the hardhat testcase you are assembling the v,r,s incorrectly.

instead of

const signature = `0x${v.toString(16)}${r.toString('hex')}${s.toString('hex')}`

you should make the signature like this.

       const signature = `0x${r.toString('hex')}${s.toString("hex")}${v.toString(

hope this helps

Your Answer

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

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