draft-EIP712Upgradeable.sol functions as javascript

Contract is using functions from:

node_modules/@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol

How can I recreate this functionality:

hash = keccak256(abi.encode(data));

_hashTypedDataV4(hash).recover(signature) == signerAddress

with javascript (ethers.js or web3 or …)

What would be the js equivalent to
keccak256(abi.encode(data))
_hashTypedDataV4
recover

functions

:1234: Code to reproduce

see example above :point_up_2:

More details, the “data” passed:
javascript:

const Types = {
	AssetType: [
		{name: 'assetClass', type: 'bytes4'},
		{name: 'data', type: 'bytes'}
	],
	Asset: [
		{name: 'assetType', type: 'AssetType'},
		{name: 'value', type: 'uint256'}
	]
};

solidity contract:

library LibAsset {

    struct AssetType {
        bytes4 assetClass;
        bytes data;
    }

    struct Asset {
        AssetType assetType;
        uint value;
    }

   function hash(AssetType memory assetType) internal pure returns (bytes32) {
        return keccak256(abi.encode(
                ASSET_TYPE_TYPEHASH,
                assetType.assetClass,
                keccak256(assetType.data)
            ));
  }
}
contract Validate {

  function validate(LibAsset.asset memory asset, bytes memory signature, address memory 
    validateAddress) internal {
    bytes32 hash = LibAsset.hash(asset);
    require(_hashTypedDataV4(hash).recover(signature) == validateAddress,
                    "User: signature verification error"
                );
  }
}

:computer: Environment

hardhat or truffle

latest hardhat or truffle version

sorry to bump @Skyge @frangio immediately after posting, unfortunately it’s a little time sensitive issue

I would assume for hashing I could use:
ethers 5.2 function:
utils.hashMessage()

for keccak256:
utils.keccak256()

what about the abi.encode(data) do I need to use this:

and how exactly?

You can’t use this. You need to use TypedDataEncoder.

What is the higher level problem that you’re trying to solve…? There may be a simpler solution than what you’re asking for.

If it’s time sensitive, you may want to evaluate other avenues to get help. We can’t guarantee a prompt reply.

If it’s time sensitive, you may want to evaluate other avenues to get help. We can’t guarantee a prompt reply.
Sure thing, was trying to explain why I bumped you guys so early after posting.

The higher level problem is:
I’m able to validate the input signature on localnet and testnets with hardhat but not with metamask

So i wanted to create a way to paste the signed input into a javascript function and see if its spits out the correct signer address.

would this from solidity

function hash(AssetType memory assetType) internal pure returns (bytes32) {
        return keccak256(abi.encode(
                ASSET_TYPE_TYPEHASH,
                assetType.assetClass,
                keccak256(assetType.data)
            ));

    function hash(Asset memory asset) internal pure returns (bytes32) {
        return keccak256(abi.encode(
                ASSET_TYPEHASH,
                hash(asset.assetType),
                asset.value
            ));
    }

correspond to this in javascript:

function hashAssetType(assetType) {
  return web3.utils.soliditySha3(
    web3.eth.abi.encodeParameters(
      ['bytes', 'bytes4', 'bytes'],
      [
        ASSET_TYPE_TYPEHASH,
        assetType.assetClass,
        assetType.data === '0x' ? '0x' : web3.utils.soliditySha3(assetType.data)
      ]
    )
  )
}

function hashAsset(asset) {
  return web3.utils.soliditySha3(
    web3.eth.abi.encodeParameters(
      ['bytes', 'bytes', 'uint'],
      [ASSET_TYPEHASH, hashAssetType(asset.assetType), asset.value]
    )
  )
}

Where you’re using bytes it’s probably wrong and should be bytes32.

You may want to look at our tests for EIP712

getting closer:


	function hashOrder(order) {
		return web3.utils.soliditySha3(
			web3.eth.abi.encodeParameters(
				[
					'bytes32',
					'address',
					'bytes32',
					'address',
					'bytes32',
					'uint',
					'uint',
					'uint',
					'bytes4',
					'bytes32'
				],
				[
					ORDER_TYPEHASH,
					order.maker,
					hashAsset(order.makeAsset),
					order.taker,
					hashAsset(order.takeAsset),
					order.salt,
					order.start,
					order.end,
					order.dataType,
					ethers.utils.keccak256(order.data)
				]
			)
		)
	}
	function hashAssetType(assetType) {
		return web3.utils.keccak256(
			web3.eth.abi.encodeParameters(
				['bytes32', 'bytes4', 'bytes32'],
				[
					ASSET_TYPE_TYPEHASH,
					assetType.assetClass,
					ethers.utils.keccak256(assetType.data)
				]
			)
		)
	}

	function hashAsset(asset) {
		return web3.utils.soliditySha3(
			web3.eth.abi.encodeParameters(
				['bytes32', 'bytes32', 'uint'],
				[ASSET_TYPEHASH, hashAssetType(asset.assetType), asset.value]
			)
		)
	}
		const testOrder = Order(accounts1, Asset(assetClass, assetData, 100), ZERO, Asset(assetClass, assetData, 200), 1, 0, 0, "0xffffffff", "0x");

		const hashedOrderFromJS = hashOrder(testOrder)
		console.log("hashOrder JS: ", hashedOrderFromJS)

		const hashedOrderFromContract = await testing.hashOrder(testOrder)
		console.log("hashedOrderFromContract: ", hashedOrderFromContract)
		expect(hashedOrderFromContract).eq(hashedOrderFromJS);

this validates so the hashing is ok now

but he signature with the Domain separator are different created from javascript and solidity _hashTypedDataV4 _domainSeparatorV4

Ganache by default returns a different chain id from within a contract execution than from the RPC endpoint, and this can cause you to see different domain separators.

This was fixed I think: https://github.com/trufflesuite/ganache-core/issues/515#issuecomment-700301787

setting the chain id while starting ganache-cli

and checking it via getters from contract and also from RPC and its the same chain id