eth-erc712

ERC712 typed data sign material builder
Log | Files | Refs

commit 0c71201e7274e9dd2ccec2ee782afb8f37669ed2
parent 4fdfc132b11a4656f138e0d5d51f692a3d85ce48
Author: lash <dev@holbrook.no>
Date:   Tue, 28 Mar 2023 17:00:27 +0100

WIP add verify example against contract, calldata fails

Diffstat:
A.gitignore | 4++++
Apython/eth_erc712/__init__.py | 1+
Apython/eth_erc712/base.py | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apython/eth_erc712/data/ERC712Example.bin | 2++
Apython/eth_erc712/data/ERC712Example.json | 1+
Apython/eth_erc712/data/ERC712Example.metadata.json | 1+
Rpython/eth_erc721/unittest/__init__.py -> python/eth_erc712/unittest/__init__.py | 0
Apython/eth_erc712/unittest/base.py | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Dpython/eth_erc721/__init__.py | 1-
Dpython/eth_erc721/base.py | 77-----------------------------------------------------------------------------
Dpython/eth_erc721/unittest/base.py | 23-----------------------
Mpython/requirements.txt | 3+--
Mpython/tests/test_basic.py | 53++++++++++++++++++++++++++++++++++++++++++-----------
Asolidity/ERC712Example.sol | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsolidity/Example.sol | 109-------------------------------------------------------------------------------
Asolidity/Makefile | 12++++++++++++
16 files changed, 362 insertions(+), 223 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +*.pyc +build/ +dist/ diff --git a/python/eth_erc712/__init__.py b/python/eth_erc712/__init__.py @@ -0,0 +1 @@ +from .base import ERC712Encoder, EIP712Domain, EIP712DomainEncoder diff --git a/python/eth_erc712/base.py b/python/eth_erc712/base.py @@ -0,0 +1,135 @@ +# standard imports +import logging +import sha3 + +# external imports +from chainlib.eth.contract import ABIContractType +from chainlib.eth.contract import ABIContractEncoder +from chainlib.hash import keccak256 +from chainlib.hash import keccak256_string_to_hex + +logg = logging.getLogger(__name__) + + +class ERC712Encoder(ABIContractEncoder): + + def __init__(self, struct_name): + super(ERC712Encoder, self).__init__() + self.method(struct_name) + self.encode = self.get_contents + + + def add(self, k, t, v): + typ_checked = ABIContractType(t.value) + self.typ_literal(t.value + ' ' + k) + m = getattr(self, t.value) + m(v) + + + def string(self, s): + v = keccak256_string_to_hex(s) + self.contents.append(v) + self.add_type(ABIContractType.STRING) + self.__log_latest_erc712(s) + + + def bytes(self, s): + v = keccak256_string_to_hex(s) + self.contents.append(v) + self.add_type(ABIContractType.BYTES) + self.__log_latest_erc712(s) + + + def __log_latest_erc712(self, v): + l = len(self.types) - 1 + logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value)) + + + def encode_type(self): + v = self.get_method() + r = keccak256(v) + logg.debug('typehash material {} -> {}'.format(v, r.hex())) + return r + + + def encode_data(self): + return b''.join(list(map(lambda x: bytes.fromhex(x), self.contents))) + + + def get_contents(self): + return self.encode_type() + self.encode_data() + + +class EIP712Domain(ERC712Encoder): + + def __init__(self, name=None, version=None, chain_id=None, verifying_contract=None, salt=None): + super(EIP712Domain, self).__init__('EIP712Domain') + if name != None: + self.add('name', ABIContractType.STRING, name) + if version != None: + self.add('version', ABIContractType.STRING, version) + if chain_id != None: + self.add('chainId', ABIContractType.UINT256, chain_id) + if verifying_contract != None: + self.add('verifyingContract', ABIContractType.ADDRESS, verifying_contract) + if salt != None: + self.add('salt', ABIContractType.BYTES32, salt) + + + def get_contents(self): + v = self.encode_type() + self.encode_data() + r = keccak256(v) + logg.debug('domainseparator material {} -> {}'.format(v.hex(), r.hex())) + return r + + +class EIP712DomainEncoder(ERC712Encoder): + + def __init__(self, struct_name, domain): + assert domain.__class__.__name__ == 'EIP712Domain' + self.domain = domain + self.__cache_data = b'' + super(EIP712DomainEncoder, self).__init__(struct_name) + + + def __cache(self): + if not self.dirty: + return + domain = self.domain.get_contents() + contents = super(EIP712DomainEncoder, self).get_contents() + self.__cache_data = domain + contents + + + def get_contents(self): + self.__cache() + return self.__cache_data + + + def get_domain(self): + self.__cache() + return self.__cache_data[:32] + + + def get_data_hash(self): + self.__cache() + return self.__cache_data[32:64] + + + def get_typed_data(self): + self.__cache() + return self.__cache_data[64:] + + + def __str__(self): + self.__cache() + domain = self.get_domain() + data_hash = self.get_data_hash() + data = self.get_typed_data() + s = 'erc712domain\t{}\nerc712type\t{}\nerc712data\n'.format( + domain.hex(), + data_hash.hex(), + ) + for i in range(0, len(data), 32): + s += '\t' + data[i:i+32].hex() + '\n' + + return s diff --git a/python/eth_erc712/data/ERC712Example.bin b/python/eth_erc712/data/ERC712Example.bin @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b50620000d46040518060a001604052806040518060400160405280600a81526020017f4574686572204d61696c0000000000000000000000000000000000000000000081525081526020016040518060400160405280600181526020017f31000000000000000000000000000000000000000000000000000000000000008152508152602001602a81526020013073ffffffffffffffffffffffffffffffffffffffff1681526020014340815250620000e0640100000000026401000000009004565b60018190555062000230565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f826000015180519060200120836020015180519060200120846040015185606001516040516020016200013b959493929190620001d3565b604051602081830303815290604052805190602001209050919050565b6000819050919050565b6200016d8162000158565b82525050565b6000819050919050565b620001888162000173565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000620001bb826200018e565b9050919050565b620001cd81620001ae565b82525050565b600060a082019050620001ea600083018862000162565b620001f9602083018762000162565b62000208604083018662000162565b6200021760608301856200017d565b620002266080830184620001c2565b9695505050505050565b610ac980620002406000396000f3fe608060405234801561001057600080fd5b506004361061005e576000357c010000000000000000000000000000000000000000000000000000000090048063bfa0b13314610063578063cb11771714610081578063f8a8fd6d146100b1575b600080fd5b61006b6100cf565b60405161007891906104b7565b60405180910390f35b61009b6004803603810190610096919061081d565b6100d5565b6040516100a891906108bb565b60405180910390f35b6100b961019d565b6040516100c691906108bb565b60405180910390f35b60005481565b6000806001546100e4876103c2565b6040516020016100f592919061094e565b60405160208183030381529060405280519060200120905085600001516020015173ffffffffffffffffffffffffffffffffffffffff166001828787876040516000815260200160405260405161014f9493929190610994565b6020604051602081039080840390855afa158015610171573d6000803e3d6000fd5b5050506020604051035173ffffffffffffffffffffffffffffffffffffffff1614915050949350505050565b600080604051806060016040528060405180604001604052806040518060400160405280600381526020017f436f770000000000000000000000000000000000000000000000000000000000815250815260200173cd2a3d9f938e13cd947ec05abc7fe734df8dd82673ffffffffffffffffffffffffffffffffffffffff16815250815260200160405180604001604052806040518060400160405280600381526020017f426f620000000000000000000000000000000000000000000000000000000000815250815260200173bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb73ffffffffffffffffffffffffffffffffffffffff1681525081526020016040518060400160405280600b81526020017f48656c6c6f2c20426f622100000000000000000000000000000000000000000081525081525090506000601c905060007f4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d600102905060007f07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b9156260010290507ff2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f60010260015414610363576103626109d9565b5b7fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e600102610390856103c2565b1461039e5761039d6109d9565b5b6103aa848484846100d5565b6103b7576103b66109d9565b5b600194505050505090565b60007fa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac26103f2836000015161043b565b6103ff846020015161043b565b84604001518051906020012060405160200161041e9493929190610a08565b604051602081830303815290604052805190602001209050919050565b60007fb9d8c78acf9b987311de6c7b45bb6a9c8e1bf361fa7fd3467a2163f994c79500826000015180519060200120836020015160405160200161048193929190610a5c565b604051602081830303815290604052805190602001209050919050565b6000819050919050565b6104b18161049e565b82525050565b60006020820190506104cc60008301846104a8565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b610534826104eb565b810181811067ffffffffffffffff82111715610553576105526104fc565b5b80604052505050565b60006105666104d2565b9050610572828261052b565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff8211156105a1576105a06104fc565b5b6105aa826104eb565b9050602081019050919050565b82818337600083830152505050565b60006105d96105d484610586565b61055c565b9050828152602081018484840111156105f5576105f4610581565b5b6106008482856105b7565b509392505050565b600082601f83011261061d5761061c61057c565b5b813561062d8482602086016105c6565b91505092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061066182610636565b9050919050565b61067181610656565b811461067c57600080fd5b50565b60008135905061068e81610668565b92915050565b6000604082840312156106aa576106a96104e6565b5b6106b4604061055c565b9050600082013567ffffffffffffffff8111156106d4576106d3610577565b5b6106e084828501610608565b60008301525060206106f48482850161067f565b60208301525092915050565b600060608284031215610716576107156104e6565b5b610720606061055c565b9050600082013567ffffffffffffffff8111156107405761073f610577565b5b61074c84828501610694565b600083015250602082013567ffffffffffffffff8111156107705761076f610577565b5b61077c84828501610694565b602083015250604082013567ffffffffffffffff8111156107a05761079f610577565b5b6107ac84828501610608565b60408301525092915050565b600060ff82169050919050565b6107ce816107b8565b81146107d957600080fd5b50565b6000813590506107eb816107c5565b92915050565b6107fa8161049e565b811461080557600080fd5b50565b600081359050610817816107f1565b92915050565b60008060008060808587031215610837576108366104dc565b5b600085013567ffffffffffffffff811115610855576108546104e1565b5b61086187828801610700565b9450506020610872878288016107dc565b935050604061088387828801610808565b925050606061089487828801610808565b91505092959194509250565b60008115159050919050565b6108b5816108a0565b82525050565b60006020820190506108d060008301846108ac565b92915050565b600081905092915050565b7f1901000000000000000000000000000000000000000000000000000000000000600082015250565b60006109176002836108d6565b9150610922826108e1565b600282019050919050565b6000819050919050565b6109486109438261049e565b61092d565b82525050565b60006109598261090a565b91506109658285610937565b6020820191506109758284610937565b6020820191508190509392505050565b61098e816107b8565b82525050565b60006080820190506109a960008301876104a8565b6109b66020830186610985565b6109c360408301856104a8565b6109d060608301846104a8565b95945050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b6000608082019050610a1d60008301876104a8565b610a2a60208301866104a8565b610a3760408301856104a8565b610a4460608301846104a8565b95945050505050565b610a5681610656565b82525050565b6000606082019050610a7160008301866104a8565b610a7e60208301856104a8565b610a8b6040830184610a4d565b94935050505056fea2646970667358221220007ab409ce7b5f3486cfc31fdec39b0e47d8290b106b2ec117428d0ea12b374764736f6c63430008130033 +\ No newline at end of file diff --git a/python/eth_erc712/data/ERC712Example.json b/python/eth_erc712/data/ERC712Example.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"salt","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"test","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"wallet","type":"address"}],"internalType":"struct Example.Person","name":"from","type":"tuple"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"wallet","type":"address"}],"internalType":"struct Example.Person","name":"to","type":"tuple"},{"internalType":"string","name":"contents","type":"string"}],"internalType":"struct Example.Mail","name":"mail","type":"tuple"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] diff --git a/python/eth_erc712/data/ERC712Example.metadata.json b/python/eth_erc712/data/ERC712Example.metadata.json @@ -0,0 +1 @@ +{"compiler":{"version":"0.8.19+commit.7dd6d404"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"salt","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"test","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"wallet","type":"address"}],"internalType":"struct Example.Person","name":"from","type":"tuple"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"address","name":"wallet","type":"address"}],"internalType":"struct Example.Person","name":"to","type":"tuple"},{"internalType":"string","name":"contents","type":"string"}],"internalType":"struct Example.Mail","name":"mail","type":"tuple"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"verify","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"ERC712Example.sol":"Example"},"evmVersion":"byzantium","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"ERC712Example.sol":{"keccak256":"0x745d4f0ccb2db6e551488cd7e3b7a0c50ce059992451afbaa962a0069143a70b","license":"CC0-1.0","urls":["bzz-raw://4c9920151c49a4219b3ca687a67b0a30d0e47d0023d6a9af7ca98f91cf7a708f","dweb:/ipfs/QmVbyN4hsg5DYchTd7ovAah2oe3tNRWWdCfmUmtFejHK2B"]}},"version":1} diff --git a/python/eth_erc721/unittest/__init__.py b/python/eth_erc712/unittest/__init__.py diff --git a/python/eth_erc712/unittest/base.py b/python/eth_erc712/unittest/base.py @@ -0,0 +1,50 @@ +# standard imports +import os +import logging + +# external imports +from chainlib.eth.unittest.ethtester import EthTesterCase +from chainlib.eth.address import to_checksum_address +from chainlib.connection import RPCConnection +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.gas import OverrideGasOracle +from chainlib.eth.gas import Gas +from chainlib.eth.tx import receipt + +# local imports +from eth_erc712 import EIP712Domain + +script_dir = os.path.realpath(os.path.dirname(__file__)) +data_dir = os.path.join(script_dir, '..', 'data') + +logg = logging.getLogger(__name__) + + +class TestERC712(EthTesterCase): + + def setUp(self): + super(TestERC712, self).setUp() + nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc) + gas_oracle = OverrideGasOracle(limit=1000000) + fp = os.path.join(data_dir, 'ERC712Example.bin') + f = open(fp, 'r') + bytecode = f.read() + f.close() + c = Gas(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle) + self.conn = RPCConnection.connect(self.chain_spec, 'default') + (tx_hash, o) = c.create(self.accounts[0], None, 0, data=bytecode) + r = self.conn.do(o) + o = receipt(r) + r = self.conn.do(o) + self.address = to_checksum_address(r['contract_address']) + logg.debug('erc712 example smart contract published with hash {} address {}'.format(r, self.address)) + + address = os.urandom(20).hex() + salt = os.urandom(32).hex() + self.domain = EIP712Domain( + name='Ether Mail', + version='1', + chain_id=42, + verifying_contract=to_checksum_address('0xcccccccccccccccccccccccccccccccccccccccc'), + salt=salt, + ) diff --git a/python/eth_erc721/__init__.py b/python/eth_erc721/__init__.py @@ -1 +0,0 @@ -from .base import ERC712Encoder, EIP712Domain diff --git a/python/eth_erc721/base.py b/python/eth_erc721/base.py @@ -1,77 +0,0 @@ -# standard imports -import logging -import sha3 - -# external imports -from chainlib.eth.contract import ABIContractType -from chainlib.eth.contract import ABIContractEncoder -from chainlib.hash import keccak256 -from chainlib.hash import keccak256_string_to_hex - -logg = logging.getLogger(__name__) - - -class ERC712Encoder(ABIContractEncoder): - - def __init__(self, struct_name): - super(ERC712Encoder, self).__init__() - self.method(struct_name) - self.ks = [] - self.encode = self.get_contents - - - def add(self, k, t, v): - typ_checked = ABIContractType(t.value) - self.typ_literal(t.value + ' ' + k) - m = getattr(self, t.value) - m(v) - - - def string(self, s): - self.types.append(ABIContractType.STRING) - v = keccak256_string_to_hex(s) - self.contents.append(v) - self.__log_latest_erc712(s) - - - def bytes(self, s): - self.types.append(ABIContractType.BYTES) - v = keccak256_string_to_hex(s) - self.contents.append(v) - self.__log_latest_erc712(s) - - - def __log_latest_erc712(self, v): - l = len(self.types) - 1 - logg.debug('Encoder added {} -> {} ({})'.format(v, self.contents[l], self.types[l].value)) - - - def encode_type(self): - v = self.get_method() - r = keccak256(v) - logg.debug('typehash material {} -> {}'.format(v, r.hex())) - return r - - - def encode_data(self): - return b''.join(list(map(lambda x: bytes.fromhex(x), self.contents))) - - - def get_contents(self): - return b'\x19\x01' + self.encode_type() + self.encode_data() - - -class EIP712Domain(ERC712Encoder): - - def __init__(self, name=None, version=None, chain_id=None, verifying_contract=None, salt=None): - super(EIP712Domain, self).__init__('EIP712Domain') - if name != None: - self.add('name', ABIContractType.STRING, name) - if version != None: - self.add('version', ABIContractType.STRING, version) - if chain_id != None: - self.add('chainId', ABIContractType.UINT256, chain_id) - if verifying_contract != None: - self.add('verifyingContract', ABIContractType.ADDRESS, verifying_contract) - if salt != None: - self.add('salt', ABIContractType.BYTES32, salt) diff --git a/python/eth_erc721/unittest/base.py b/python/eth_erc721/unittest/base.py @@ -1,23 +0,0 @@ -# standard imports -import os - -# external imports -from chainlib.eth.unittest.ethtester import EthTesterCase -from chainlib.eth.address import to_checksum_address - -# local imports -from eth_erc721 import EIP712Domain - - -class TestERC712(EthTesterCase): - - def setUp(self): - address = os.urandom(20).hex() - salt = os.urandom(32).hex() - self.struct = EIP712Domain( - name='Ether Mail', - version='1', - chain_id=42, - verifying_contract=to_checksum_address('0xcccccccccccccccccccccccccccccccccccccccc'), - salt=salt, - ) diff --git a/python/requirements.txt b/python/requirements.txt @@ -1,2 +1 @@ -chainlib-eth==0.4.17 -pysha3==1.0.2 +chainlib-eth~=0.4.18 diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py @@ -5,10 +5,17 @@ import os # external imports from chainlib.eth.contract import ABIContractType +from chainlib.eth.tx import TxFactory +from chainlib.eth.tx import TxFormat +from chainlib.jsonrpc import JSONRPCRequest +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType +from hexathon import add_0x # local imports -from eth_erc721.unittest import TestERC712 as TestERC712Base -from eth_erc721 import ERC712Encoder +from eth_erc712.unittest import TestERC712 as TestERC712Base +from eth_erc712 import ERC712Encoder +from eth_erc712 import EIP712DomainEncoder logging.basicConfig(level=logging.DEBUG) logg = logging.getLogger() @@ -22,12 +29,11 @@ class ExamplePerson(ERC712Encoder): self.add('wallet', ABIContractType.ADDRESS, wallet) -class ExampleMail(ERC712Encoder): - - def __init__(self, from_name, from_wallet, to_name, to_wallet, contents): +class ExampleMail(EIP712DomainEncoder): + def __init__(self, from_name, from_wallet, to_name, to_wallet, contents, *args, **kwargs): self.pfrom = ExamplePerson(from_name, from_wallet) self.pto = ExamplePerson(to_name, to_wallet) - super(ExampleMail, self).__init__('Mail') + super(ExampleMail, self).__init__('Mail', *args, **kwargs) self.typ_literal('Person from') self.typ_literal('Person to') self.add('contents', ABIContractType.STRING, contents) @@ -51,21 +57,46 @@ class ExampleMail(ERC712Encoder): class TestERC712(TestERC712Base): def test_domain_separator(self): - r = self.struct.encode_type() + r = self.domain.encode_type() self.assertEqual(r, bytes.fromhex('d87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472')) def test_domain_data(self): - r = self.struct.get() + r = self.domain.get() print(r.hex()) def test_mail(self): a = os.urandom(20).hex() b = os.urandom(20).hex() - o = ExampleMail('Pinky Inky', a, 'Clyde Blinky', b, 'barbarbar') - r = o.get() - print(r.hex()) + mail = ExampleMail('Pinky Inky', a, 'Clyde Blinky', b, 'barbarbar', domain=self.domain) + sig = self.signer.sign_typed_message(self.accounts[0], mail.get_domain(), mail.get_data_hash()) + sig = sig[:64] + (sig[64] + 27).to_bytes(1, byteorder='big') + logg.debug('message is:\n{}\nsigned by {}'.format(mail, self.accounts[0])) + + c = TxFactory(self.chain_spec) + j = JSONRPCRequest() + o = j.template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('verify') + enc.typ_literal('Mail') + enc.typ(ABIContractType.UINT8) + enc.typ(ABIContractType.BYTES32) + enc.typ(ABIContractType.BYTES32) + enc.bytes(mail.get_typed_data().hex()) + enc.uintn(sig[64], 8) + enc.bytes32(sig[:32]) + enc.bytes32(sig[32:64]) + data = add_0x(enc.get()) + tx = c.template(self.accounts[0], self.address) + tx = c.set_code(tx, data) + o['params'].append(c.normalize(tx)) + o['params'].append('latest') + o = j.finalize(o) + r = self.rpc.do(o) + r = strip_0x(r) + self.assertEqual(int(r, 16), 1) if __name__ == '__main__': diff --git a/solidity/ERC712Example.sol b/solidity/ERC712Example.sol @@ -0,0 +1,113 @@ +pragma solidity >0.6.11; + +// SPDX-License-Identifier: CC0-1.0 +// This file was copied from https://eips.ethereum.org/assets/eip-712/Example.sol +// Slightly edited to make it work with test harness parameters + +contract Example { + + struct EIP712Domain { + string name; + string version; + uint256 chainId; + address verifyingContract; + bytes32 salt; + } + + struct Person { + string name; + address wallet; + } + + struct Mail { + Person from; + Person to; + string contents; + } + + bytes32 public salt; + + bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ); + + bytes32 constant PERSON_TYPEHASH = keccak256( + "Person(string name,address wallet)" + ); + + bytes32 constant MAIL_TYPEHASH = keccak256( + "Mail(Person from,Person to,string contents)Person(string name,address wallet)" + ); + + bytes32 DOMAIN_SEPARATOR; + + constructor () public { + DOMAIN_SEPARATOR = hash(EIP712Domain({ + name: "Ether Mail", + version: '1', + chainId: 42, + verifyingContract: address(this), + salt: blockhash(block.number) + })); + } + + function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) { + return keccak256(abi.encode( + EIP712DOMAIN_TYPEHASH, + keccak256(bytes(eip712Domain.name)), + keccak256(bytes(eip712Domain.version)), + eip712Domain.chainId, + eip712Domain.verifyingContract + )); + } + + function hash(Person memory person) internal pure returns (bytes32) { + return keccak256(abi.encode( + PERSON_TYPEHASH, + keccak256(bytes(person.name)), + person.wallet + )); + } + + function hash(Mail memory mail) internal pure returns (bytes32) { + return keccak256(abi.encode( + MAIL_TYPEHASH, + hash(mail.from), + hash(mail.to), + keccak256(bytes(mail.contents)) + )); + } + + function verify(Mail memory mail, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + // Note: we need to use `encodePacked` here instead of `encode`. + bytes32 digest = keccak256(abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + hash(mail) + )); + return ecrecover(digest, v, r, s) == mail.from.wallet; + } + + function test() public view returns (bool) { + // Example signed message + Mail memory mail = Mail({ + from: Person({ + name: "Cow", + wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 + }), + to: Person({ + name: "Bob", + wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB + }), + contents: "Hello, Bob!" + }); + uint8 v = 28; + bytes32 r = 0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d; + bytes32 s = 0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562; + + assert(DOMAIN_SEPARATOR == 0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f); + assert(hash(mail) == 0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e); + assert(verify(mail, v, r, s)); + return true; + } +} diff --git a/solidity/Example.sol b/solidity/Example.sol @@ -1,109 +0,0 @@ -pragma solidity ^0.4.24; - -// This file was copied from https://eips.ethereum.org/assets/eip-712/Example.sol -// SPDX-License-Identifier: CC0-1.0 - -contract Example { - - struct EIP712Domain { - string name; - string version; - uint256 chainId; - address verifyingContract; - } - - struct Person { - string name; - address wallet; - } - - struct Mail { - Person from; - Person to; - string contents; - } - - bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256( - "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" - ); - - bytes32 constant PERSON_TYPEHASH = keccak256( - "Person(string name,address wallet)" - ); - - bytes32 constant MAIL_TYPEHASH = keccak256( - "Mail(Person from,Person to,string contents)Person(string name,address wallet)" - ); - - bytes32 DOMAIN_SEPARATOR; - - constructor () public { - DOMAIN_SEPARATOR = hash(EIP712Domain({ - name: "Ether Mail", - version: '1', - chainId: 1, - // verifyingContract: this - verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC - })); - } - - function hash(EIP712Domain eip712Domain) internal pure returns (bytes32) { - return keccak256(abi.encode( - EIP712DOMAIN_TYPEHASH, - keccak256(bytes(eip712Domain.name)), - keccak256(bytes(eip712Domain.version)), - eip712Domain.chainId, - eip712Domain.verifyingContract - )); - } - - function hash(Person person) internal pure returns (bytes32) { - return keccak256(abi.encode( - PERSON_TYPEHASH, - keccak256(bytes(person.name)), - person.wallet - )); - } - - function hash(Mail mail) internal pure returns (bytes32) { - return keccak256(abi.encode( - MAIL_TYPEHASH, - hash(mail.from), - hash(mail.to), - keccak256(bytes(mail.contents)) - )); - } - - function verify(Mail mail, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) { - // Note: we need to use `encodePacked` here instead of `encode`. - bytes32 digest = keccak256(abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - hash(mail) - )); - return ecrecover(digest, v, r, s) == mail.from.wallet; - } - - function test() public view returns (bool) { - // Example signed message - Mail memory mail = Mail({ - from: Person({ - name: "Cow", - wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826 - }), - to: Person({ - name: "Bob", - wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB - }), - contents: "Hello, Bob!" - }); - uint8 v = 28; - bytes32 r = 0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d; - bytes32 s = 0x07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b91562; - - assert(DOMAIN_SEPARATOR == 0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f); - assert(hash(mail) == 0xc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e); - assert(verify(mail, v, r, s)); - return true; - } -} diff --git a/solidity/Makefile b/solidity/Makefile @@ -0,0 +1,12 @@ +SOLC = /usr/bin/solc + +all: + $(SOLC) --bin ERC712Example.sol --evm-version byzantium | awk 'NR>3' > ERC712Example.bin + $(SOLC) --abi ERC712Example.sol --evm-version byzantium | awk 'NR>3' > ERC712Example.json + $(SOLC) --metadata ERC712Example.sol --evm-version byzantium | awk 'NR>3' > ERC712Example.metadata.json + truncate -s -1 ERC712Example.bin + +install: all + cp -v *.json ../python/eth_erc712/data/ + cp -v *.bin ../python/eth_erc712/data/ +