Nethereum / Nethereum

Ethereum .Net cross platform integration library
http://nethereum.com
MIT License
2.16k stars 725 forks source link

Nethereum.EIP712 cannot verify Metamask signatures signed with eth_signTypedDataV4 #854

Closed adzee95 closed 2 years ago

adzee95 commented 2 years ago

I cannot verify signatures signed using Metamask, calling eth_signTypedDataV4 through JS.

I'm sending this JSON as parameter:

{
  "types": {
    "EIP712Domain": [
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "version",
        "type": "string"
      },
      {
        "name": "chainId",
        "type": "uint256"
      }
    ],
    "Mail": [
      {
        "name": "contents",
        "type": "string"
      }
    ]
  },
  "primaryType": "Mail",
  "domain": {
    "name": "SenseNet",
    "version": "1",
    "chainId": 1
  },
  "message": {
    "contents": "salut"
  }
}

Which metamask signs, showing contents : salut in the dialog.

Signing this with address 0x371F69126d40a7fcf5613Afe61eD169Ac5cBD780 results in signature 0x017111316e102b7002d6d04d0979e5742382b765bac324c35fd31d4b61f0f48626a77917c7881cbe33db3f0da647ef17ec11917a3368c08cb67eee891d4c02481c

When I try to recreate the signature on the back-end, I use the following structure:

    public class Domain : IDomain
    {
        public string? Name { get; set; }
        public string? Version { get; set; }
        public BigInteger? ChainId { get; set; }
    }

    public class Message 
    {
        public string contents;
    }

    TypedData<Domain> typedData = new TypedData<Domain>()
      {
          Domain = new Domain
          {
              Name = "SenseNet",
              Version = "1",
              ChainId = 1
          },
          Types = new Dictionary<string, MemberDescription[]>
          {
              ["EIP712Domain"] = new[]
              {
                  new MemberDescription { Name = "name", Type = "string" },
                  new MemberDescription { Name = "version", Type = "string" },
                  new MemberDescription { Name = "chainId", Type = "uint256" }
              },

              ["Mail"] = new[]
              {
                  new MemberDescription { Name = "contents", Type = "string" }
              }
          },
          PrimaryType = "Mail"

      };

      var mail = new Mail()
      {
          contents = "salut"
      }

Followed by verification:

var result = "0x017111316e102b7002d6d04d0979e5742382b765bac324c35fd31d4b61f0f48626a77917c7881cbe33db3f0da647ef17ec11917a3368c08cb67eee891d4c02481c" 
var signer = new Eip712TypedDataSigner();
var addr = signer.RecoverFromSignatureV4(mail, typedData, result);

But the address returned 0x8E20aEE5eB0FBC0a20ee1D80757a69f4ed392c42 is not correct.

I can't figure out what I'm doing wrong. I tried following all steps in https://github.com/Nethereum/Nethereum/issues/634 including the advice on how to recover the signature.

juanfranblanco commented 2 years ago

Use this Domain instead

 [Struct("EIP712Domain")]
    public class DomainWithNameVersionAndChainId: IDomain
    {
        [Parameter("string", "name", 1)]
        public virtual string Name { get; set; }

        [Parameter("string", "version", 2)]
        public virtual string Version { get; set; }

        [Parameter("uint256", "chainId", 3)]
        public virtual BigInteger? ChainId { get; set; }

    }
adzee95 commented 2 years ago

Using this worked like a charm. How can this be! Is it because I left out the struct and parameter attributes?

juanfranblanco commented 2 years ago

Yes, that was it. It needs the parameters. I am adding this one also as a preset one.

juanfranblanco commented 2 years ago

Typed example:

private readonly Eip712TypedDataSigner _signer = new Eip712TypedDataSigner();

        //Message types for easier input
        [Struct("Mail")]
        public class Mail
        {
            [Parameter("string", "contents", 1)]
            public string Contents { get; set; }
        }

        //The generic EIP712 Typed schema defintion for this message
        public TypedData<DomainWithNameVersionAndChainId> GetMailTypedDefinition()
        {
            return new TypedData<DomainWithNameVersionAndChainId>
            {
                Domain = new DomainWithNameVersionAndChainId
                {
                    Name = "SenseNet",
                    Version = "1",
                    ChainId = 1
                },
                Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(DomainWithNameVersionAndChainId), typeof(Mail)),
                PrimaryType = nameof(Mail),
            };
        }

        [Fact]
        private void ComplexMessageTypedDataEncodingShouldBeCorrectForV4IncludingArraysAndTypes()
        {
            var typedData = GetMailTypedDefinition();

            var mail = new Mail
            {
                Contents = "salut"
            };

            typedData.Domain.ChainId = 1;

            var key = new EthECKey("94e001d6adf3a3275d5dd45971c2a5f6637d3e9c51f9693f2e678f649e164fa5");

            var signature = _signer.SignTypedDataV4(mail, typedData, key);

            Assert.Equal("0xba0ae54dd4e2a9e55f67cc096af195fadf70a6b8e310fdfe253a90bc79a130e43db1e1b0fa419ae364f43a8843e889ccd89f473326141b78bdf420a80fc891d31b", signature);

            var addressRecovered = _signer.RecoverFromSignatureV4(mail, typedData, signature);
            var address = key.GetPublicAddress();

            Assert.True(address.IsTheSameAddress(addressRecovered));

        }