I am using web3.js library to sign a transaction and then verify it from the smart contract. The Signature generated on FrontEnd and as well as from the smart contract are not same..

This is the frontend code

const DomainSeparator = ethers.keccak256(
          ["string", "address"],
          ["0x01", contractAddress], //**This is the deployed zeroToken contract address**
      const message = ethers.keccak256(
          ["address", "address", "uint256", "uint256"],
            fromAddress,//signer address
            toAddress,// spender address
            1000, // amount
            0, // nonce
      let finalHash = ethers.keccak256(
          ["bytes1", "bytes1", "bytes32", "bytes32"],
          ["0x19", "0x01", DomainSeparator, message]

The signature which I received on the frontend is

signature: 0xb1fce7fbe4232fb2d78e2a8633be4e550139dadfd6a643fc47852ad9848961b20646e5fb8a755529bb1735067e527255537bb04999f8a0e1339a0e78f64c52411b

**This is the solidity smart contract code which is ERC20 basically in which the the transferWithPermit takes argument of from (signer'address) which is the signer of the transaction majorly the owner's Address, to(address) which becomes the spender of the transaction, amount (to be spended from signer's account by to's address), signature which is generated using ecsign. The problem where I am receiving is on _validate() which inturn showing me the INVALID SIGNATURE, while I am debugging it I found recovered Address not as same as signer's address **

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

contract ZeroToken is ERC20 {

    mapping(address=>uint256) nonce;
    constructor() ERC20("Zero Token", "Zero") {}

    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 getDomainSeparator() internal view returns (bytes32) {
        return keccak256(abi.encode("0x01", address(this)));

    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");


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

        assembly {
            // First 32 bytes are the `r` value
            r := mload(add(signature, 32))

            // Next 32 bytes are the `s` value
            s := mload(add(signature, 64))

            // The last byte is the `v` value
            v := byte(0, mload(add(signature, 96)))


This is my hardhat test case in which I am testing my transferWithPermit()

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)

"This is the signature which is I am passing on my transferWithPermit() argument using ecsign , which is not the same." Signature: 0x4a105acbe26edb7a27ccffec3327a36f0738ece2c0ee8e45b63e3c4153c60a062afc6ead46490d82c302582da7d70e840797d7f4b51ef0feb8b12c3ea94695881b


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.