Solidity.

Post

Share your knowledge.

Christian O'Connor.
May 27, 2023
Expert Q&A

Solidity and ethers.js Compute Different Addresses from the Same Signature

This script:

const { ethers } = require('ethers');

async function recoverSigner(address, nonce, deadline, v, r, s) {
    const domain = {
        name: "RandomReachDebug5Local",
        version: "1",
        chainId: 31337,
        verifyingContract: "0x8464135c8F25Da09e49BC8782676a84730C318bC",
    };
    const types = {
        RequestRandomNFT: [
            { name: 'minter', type: 'address' },
            { name: 'nonce', type: 'uint256' },
            { name: 'deadline', type: 'uint256' },
        ],
    };
    const value = {
        minter: address,
        nonce: nonce,
        deadline: deadline,
    };

    // Get the digest of the message
    const digest = ethers.utils._TypedDataEncoder.hash(domain, types, value);
    
    const sig = {
        r: r,
        s: s,
        v: v,
    };

    if (sig.v < 27) {
        sig.v += 27;
    }

    const signer = ethers.utils.recoverAddress(digest, sig);
    
    return signer;
}

const address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
const nonce = "0";
const deadline = "1685162164";
const v = "28";
const r = "0xe0e4cc2f8b6bac3784f9feb3db4382291631d5f317d08a0b82305e5eb5ffc60a";
const s = "0x0416d521d12e26b8fc1f4bd566d5d67c5dffebe9564fe90db4f12c0a69b63d5e";

async function run() {
    try {
        const signer = await recoverSigner(address, nonce, deadline, v, r, s);
        console.log(`The signer is: ${signer}`);
    } catch (error) {
        console.error(`Error in recovering signer: ${error}`);
    }
}

run();

Produces this output: The signer is: 0x1AB26702A8068a247BD33a9555dfEf791d2BD68D

But this solidity contract:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";

contract GetAddressFromSig is ERC721, EIP712 {
    struct Request {
        address minter;
        uint256 nonce;
        uint256 deadline;
    }

    bytes32 public constant REQUEST_TYPEHASH = keccak256("Request(address minter,uint256 nonce,uint256 deadline)");

    // Initialize _DOMAIN_SEPARATOR directly with static values
    bytes32 private immutable _DOMAIN_SEPARATOR = keccak256(
        abi.encode(
            keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
            keccak256(bytes("RandomReachDebug5Local")), // static name
            keccak256(bytes("1")), // static version
            31337, // static chainId
            0x8464135c8F25Da09e49BC8782676a84730C318bC // static verifyingContract
        )
    );

    constructor(string memory name, string memory symbol) ERC721(name, symbol) EIP712(name, "1") {}

    function domainSeparator() public view returns (bytes32) {
        return _DOMAIN_SEPARATOR;
    }

    function recoverSigner(Request memory request, uint8 v, bytes32 r, bytes32 s) public view returns (address) {
        bytes32 digest = keccak256(
            abi.encodePacked(
                "\x19\x01",
                _DOMAIN_SEPARATOR,
                keccak256(
                    abi.encode(
                        REQUEST_TYPEHASH,
                        request.minter,
                        request.nonce,
                        request.deadline
                    )
                )
            )
        );

        // ECDSA.recover returns the address that is associated with the public key 
        // that was used to sign the given data, in this case, the digest.
        return ECDSA.recover(digest, v, r, s);
    }
}

Deployed with this script in hardhat:

import { ethers } from "hardhat";

const hre = require("hardhat");
const dotenv = require("dotenv");
dotenv.config();

async function main() {
  const GetAddressFromSig = await hre.ethers.getContractFactory("GetAddressFromSig");
  const gasPrice = await GetAddressFromSig.signer.getGasPrice();
  console.log(`Current gas price: ${gasPrice}`);
  const estimatedGas = await GetAddressFromSig.signer.estimateGas(
    GetAddressFromSig.getDeployTransaction("RANDOM NFT 5", "RNFT5"),
  );
  console.log(`Estimated gas: ${estimatedGas}`);
  const deploymentPrice = gasPrice.mul(estimatedGas);
  const deployerBalance = await GetAddressFromSig.signer.getBalance();
  console.log(`Deployer balance:  ${ethers.utils.formatEther(deployerBalance)}`);
  console.log(`Deployment price:  ${ethers.utils.formatEther(deploymentPrice)}`);
  const getAddressFromSig = await GetAddressFromSig.deploy("RANDOM NFT 5", "RNFT5");

  await getAddressFromSig.deployed();

  console.log("GetAddressFromSig deployed to:", getAddressFromSig.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

And ran with these commands in the hardhat console:

const Token = await ethers.getContractFactory("GetAddressFromSig");
const token = await Token.attach("0x8464135c8F25Da09e49BC8782676a84730C318bC");
const request = { minter: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", nonce: ethers.BigNumber.from("0"), deadline: ethers.BigNumber.from("1685162164") };
const v = 28
const r = "0xe0e4cc2f8b6bac3784f9feb3db4382291631d5f317d08a0b82305e5eb5ffc60a";
const s = "0x0416d521d12e26b8fc1f4bd566d5d67c5dffebe9564fe90db4f12c0a69b63d5e";
const signer = await token.recoverSigner(request, v, r, s);
console.log(`The signer is: ${signer}`);

Gives this output: The signer is: 0xeB7265f6625EaE66403a637c073E63ccf33b8Cdc

So why does the javascript calculate 0x1AB26702A8068a247BD33a9555dfEf791d2BD68D, while hardhat's solidity compiler calculates 0xeB7265f6625EaE66403a637c073E63ccf33b8Cdc?

  • Smart Contract
  • Solidity
2
1
Share
Comments
.

Answers

1
Sergey Ilin.
Jun 2 2023, 05:35

I see one mismatch. REQUEST_TYPEHASH uses the type name Request. Type name in JavaScript when defining type is RequestRandomNFT.

Also, you can try this alternative code for signing in JavaScript:

   const value = {
        minter: address,
        nonce: nonce,
        deadline: deadline,
    };

    const domainType = [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' }
    ];

    const requestType = [
            { name: 'minter', type: 'address' },
            { name: 'nonce', type: 'uint256' },
            { name: 'deadline', type: 'uint256' },
    ];

    const domainData = {
      name: 'RandomReachDebug5Local',
      version: '1',
      chainId: 31337,
      verifyingContract: '0x8464135c8F25Da09e49BC8782676a84730C318bC',
      
    };

    const dataToSign = JSON.stringify({
      types: {
        EIP712Domain: domainType,
        Request: requestType,
      },
      domain: domainData,
      primaryType: 'Request',
      value,
    });

    const signature = await provider.send('eth_signTypedData_v4', [signerAddress, dataToSign]);
0
Best Answer
Comments
.

Do you know the answer?

Please log in and share it.

We use cookies to ensure you get the best experience on our website.
More info