diff --git a/tests/integration/go_ethereum/common.py b/tests/integration/go_ethereum/common.py index e1db920c20..5d779c291a 100644 --- a/tests/integration/go_ethereum/common.py +++ b/tests/integration/go_ethereum/common.py @@ -71,6 +71,14 @@ def test_eth_chainId(self, web3): pytest.xfail('eth_chainId not implemented in geth 1.7.2') super().test_eth_chainId(web3) + def test_eth_signTypedData(self, + web3, + unlocked_account_dual_type): + pytest.xfail('eth_signTypedData JSON RPC call has not been released in geth') + super().test_eth_signTypedData( + web3, unlocked_account_dual_type + ) + class GoEthereumVersionModuleTest(VersionModuleTest): pass diff --git a/tests/integration/parity/common.py b/tests/integration/parity/common.py index 167f2bfb87..ba5fb23d82 100644 --- a/tests/integration/parity/common.py +++ b/tests/integration/parity/common.py @@ -168,6 +168,14 @@ def test_eth_getLogs_without_logs(self, web3, block_with_txn_with_log): result = web3.eth.getLogs(filter_params) assert len(result) == 0 + def test_eth_signTypedData(self, + web3, + unlocked_account_dual_type): + pytest.xfail('eth_signTypedData JSON RPC call has not been released in parity') + super().test_eth_signTypedData( + web3, unlocked_account_dual_type + ) + class ParityTraceModuleTest(TraceModuleTest): pass diff --git a/tests/integration/parity/test_parity_http.py b/tests/integration/parity/test_parity_http.py index 0765e2636f..2b58ed677e 100644 --- a/tests/integration/parity/test_parity_http.py +++ b/tests/integration/parity/test_parity_http.py @@ -49,6 +49,7 @@ def parity_command_arguments( '--password', passwordfile, '--jsonrpc-port', rpc_port, '--jsonrpc-apis', 'all', + '--jsonrpc-experimental', '--no-ipc', '--no-ws', '--whisper', @@ -65,6 +66,7 @@ def parity_import_blocks_command(parity_binary, rpc_port, datadir, passwordfile) '--password', passwordfile, '--jsonrpc-port', str(rpc_port), '--jsonrpc-apis', 'all', + '--jsonrpc-experimental', '--no-ipc', '--no-ws', '--tracing', 'on', diff --git a/tests/integration/parity/test_parity_ipc.py b/tests/integration/parity/test_parity_ipc.py index a41b2e82c6..5a204d0469 100644 --- a/tests/integration/parity/test_parity_ipc.py +++ b/tests/integration/parity/test_parity_ipc.py @@ -47,6 +47,7 @@ def parity_command_arguments( '--unlock', author, '--password', passwordfile, '--ipc-apis', 'all', + '--jsonrpc-experimental', '--no-jsonrpc', '--no-ws', '--whisper', @@ -63,6 +64,7 @@ def parity_import_blocks_command(parity_binary, ipc_path, datadir, passwordfile) '--base-path', datadir, '--password', passwordfile, '--ipc-apis', 'all', + '--jsonrpc-experimental', '--no-jsonrpc', '--no-ws', '--tracing', 'on', diff --git a/tests/integration/parity/test_parity_ws.py b/tests/integration/parity/test_parity_ws.py index baf8ef3648..a0e06f4384 100644 --- a/tests/integration/parity/test_parity_ws.py +++ b/tests/integration/parity/test_parity_ws.py @@ -51,6 +51,7 @@ def parity_command_arguments( '--ws-port', ws_port, '--ws-origins', '*', '--ws-apis', 'all', + '--jsonrpc-experimental', '--no-ipc', '--no-jsonrpc', '--whisper', @@ -68,6 +69,7 @@ def parity_import_blocks_command(parity_binary, ws_port, datadir, passwordfile): '--ws-port', str(ws_port), '--ws-origins', '*', '--ws-apis', 'all', + '--jsonrpc-experimental', '--no-ipc', '--no-jsonrpc', '--tracing', 'on', diff --git a/web3/_utils/module_testing/eth_module.py b/web3/_utils/module_testing/eth_module.py index dc96dd01be..562e56f05a 100644 --- a/web3/_utils/module_testing/eth_module.py +++ b/web3/_utils/module_testing/eth_module.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +import json import pytest from eth_abi import ( @@ -202,6 +203,111 @@ def test_eth_sign(self, web3, unlocked_account_dual_type): ) assert new_signature != signature + def test_eth_signTypedData(self, web3, unlocked_account_dual_type): + validJSONMessage = ''' + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ], + "Mail": [ + {"name": "from", "type": "Person"}, + {"name": "to", "type": "Person"}, + {"name": "contents", "type": "string"} + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x01", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ''' + signature = HexBytes(web3.eth.signTypedData( + json.loads(validJSONMessage), + HexBytes(unlocked_account_dual_type).hex() + )) + assert len(signature) == 32 + 32 + 1 + + # def test_eth_signTypedData(self, web3, unlocked_account_dual_type): + # validJSONMessage = ''' + # { + # "types": { + # "EIP712Domain": [ + # {"name": "name", "type": "string"}, + # {"name": "version", "type": "string"}, + # {"name": "chainId", "type": "uint256"}, + # {"name": "verifyingContract", "type": "address"} + # ], + # "Person": [ + # {"name": "name", "type": "string"}, + # {"name": "wallet", "type": "address"} + # ], + # "Mail": [ + # {"name": "from", "type": "Person"}, + # {"name": "to", "type": "Person"}, + # {"name": "contents", "type": "string"} + # ] + # }, + # "primaryType": "Mail", + # "domain": { + # "name": "Ether Mail", + # "version": "1", + # "chainId": "0x01", + # "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + # }, + # "message": { + # "from": { + # "name": "Cow", + # "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + # }, + # "to": { + # "name": "Bob", + # "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + # }, + # "contents": "Hello, Bob!" + # } + # } + # ''' + # import json + # signature = web3.eth.signTypedData( + # HexBytes(unlocked_account_dual_type).hex(), + # json.loads(validJSONMessage), + # "web3py-test" + # ) + # signature = HexBytes(signature) + # # assert isinstance(signature, HexBytes) + # # signature = bytes(signature) + # # assert is_bytes(signature) + # # assert len(signature) == 32 + 32 + 1 + # + # expected_signature = HexBytes( + # "0xc8b56aaeefd10ab4005c2455daf28d9082af661ac347cd" + # "b612d5b5e11f339f2055be831bf57a6e6cb5f6d93448fa35" + # "c1bd56fe1d745ffa101e74697108668c401c" + # ) + # assert signature == expected_signature + def test_eth_signTransaction(self, web3, unlocked_account): txn_params = { 'from': unlocked_account, diff --git a/web3/_utils/module_testing/personal_module.py b/web3/_utils/module_testing/personal_module.py index 82a54f7821..189d279896 100644 --- a/web3/_utils/module_testing/personal_module.py +++ b/web3/_utils/module_testing/personal_module.py @@ -1,3 +1,4 @@ +import json import pytest from eth_utils import ( @@ -89,6 +90,66 @@ def test_personal_sign_and_ecrecover(self, signer = web3.geth.personal.ecRecover(message, signature) assert is_same_address(signer, unlockable_account_dual_type) + def test_personal_sign_typed_data(self, + web3, + unlockable_account_dual_type, + unlockable_account_pw): + from hexbytes import HexBytes + typed_message = ''' + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ], + "Mail": [ + {"name": "from", "type": "Person"}, + {"name": "to", "type": "Person"}, + {"name": "contents", "type": "string"} + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x01", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ''' + signature = HexBytes(web3.geth.personal.signTypedData( + json.loads(typed_message), + HexBytes(unlockable_account_dual_type).hex(), + unlockable_account_pw + )) + + expected_signature = HexBytes( + "0xc8b56aaeefd10ab4005c2455daf28d9082af661ac347cd" + "b612d5b5e11f339f2055be831bf57a6e6cb5f6d93448fa35" + "c1bd56fe1d745ffa101e74697108668c401c" + ) + assert signature == expected_signature + assert len(signature) == 32 + 32 + 1 + # signer = web3.geth.personal.ecRecover(message, signature) + # assert is_same_address(signer, unlockable_account_dual_type) + class ParityPersonalModuleTest(): def test_personal_listAccounts(self, web3): @@ -169,3 +230,63 @@ def test_personal_sign_and_ecrecover(self, ) signer = web3.parity.personal.ecRecover(message, signature) assert is_same_address(signer, unlockable_account_dual_type) + + def test_personal_sign_typed_data(self, + web3, + unlockable_account_dual_type, + unlockable_account_pw): + from hexbytes import HexBytes + typed_message = ''' + { + "types": { + "EIP712Domain": [ + {"name": "name", "type": "string"}, + {"name": "version", "type": "string"}, + {"name": "chainId", "type": "uint256"}, + {"name": "verifyingContract", "type": "address"} + ], + "Person": [ + {"name": "name", "type": "string"}, + {"name": "wallet", "type": "address"} + ], + "Mail": [ + {"name": "from", "type": "Person"}, + {"name": "to", "type": "Person"}, + {"name": "contents", "type": "string"} + ] + }, + "primaryType": "Mail", + "domain": { + "name": "Ether Mail", + "version": "1", + "chainId": "0x01", + "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC" + }, + "message": { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" + } + } + ''' + signature = HexBytes(web3.geth.personal.signTypedData( + json.loads(typed_message), + HexBytes(unlockable_account_dual_type).hex(), + unlockable_account_pw + )) + + expected_signature = HexBytes( + "0xc8b56aaeefd10ab4005c2455daf28d9082af661ac347cd" + "b612d5b5e11f339f2055be831bf57a6e6cb5f6d93448fa35" + "c1bd56fe1d745ffa101e74697108668c401c" + ) + assert signature == expected_signature + assert len(signature) == 32 + 32 + 1 + # signer = web3.geth.personal.ecRecover(message, signature) + # assert is_same_address(signer, unlockable_account_dual_type) diff --git a/web3/_utils/personal.py b/web3/_utils/personal.py index 43ffbd32ca..7796483e39 100644 --- a/web3/_utils/personal.py +++ b/web3/_utils/personal.py @@ -45,6 +45,12 @@ ) +signTypedData = Method( + "personal_signTypedData", + mungers=[default_root_munger], +) + + ecRecover = Method( "personal_ecRecover", mungers=[default_root_munger], diff --git a/web3/_utils/rpc_abi.py b/web3/_utils/rpc_abi.py index 880a57e69e..8dc523d129 100644 --- a/web3/_utils/rpc_abi.py +++ b/web3/_utils/rpc_abi.py @@ -52,6 +52,7 @@ 'eth_sendTransaction': TRANSACTION_PARAMS_ABIS, 'eth_signTransaction': TRANSACTION_PARAMS_ABIS, 'eth_sign': ['address', 'bytes'], + 'eth_signTypedData': ['address', None], 'eth_submitHashrate': ['uint', 'bytes32'], 'eth_submitWork': ['bytes8', 'bytes32', 'bytes32'], # personal diff --git a/web3/eth.py b/web3/eth.py index b8f442aaa3..79f874e478 100644 --- a/web3/eth.py +++ b/web3/eth.py @@ -320,6 +320,11 @@ def signTransaction(self, transaction): "eth_signTransaction", [transaction], ) + def signTypedData(self, account, jsonMessage): + return self.web3.manager.request_blocking( + "eth_signTypedData", [account, jsonMessage], + ) + @apply_to_return_value(HexBytes) def call(self, transaction, block_identifier=None): # TODO: move to middleware diff --git a/web3/geth.py b/web3/geth.py index 8b6d926761..41a8185706 100644 --- a/web3/geth.py +++ b/web3/geth.py @@ -30,6 +30,7 @@ newAccount, sendTransaction, sign, + signTypedData, unlockAccount, ) from web3._utils.txpool import ( @@ -58,6 +59,7 @@ class GethPersonal(ModuleV2): newAccount = newAccount sendTransaction = sendTransaction sign = sign + signTypedData = signTypedData unlockAccount = unlockAccount diff --git a/web3/middleware/exception_retry_request.py b/web3/middleware/exception_retry_request.py index 56210648f5..8ec90b5365 100644 --- a/web3/middleware/exception_retry_request.py +++ b/web3/middleware/exception_retry_request.py @@ -47,6 +47,7 @@ 'eth_getCompilers', 'eth_getWork', 'eth_sign', + 'eth_signTypedData', 'eth_sendRawTransaction', 'personal_importRawKey', 'personal_newAccount', diff --git a/web3/middleware/pythonic.py b/web3/middleware/pythonic.py index 7296c0a2d0..5ad2efb3cf 100644 --- a/web3/middleware/pythonic.py +++ b/web3/middleware/pythonic.py @@ -355,6 +355,7 @@ def to_hexbytes(num_bytes, val, variable_length=False): 'eth_sendTransaction': to_hexbytes(32), 'eth_signTransaction': apply_formatter_if(is_not_null, signed_tx_formatter), 'eth_sign': HexBytes, + 'eth_signTypedData': HexBytes, 'eth_syncing': apply_formatter_if(is_not_false, syncing_formatter), # personal 'personal_importRawKey': to_checksum_address, diff --git a/web3/parity.py b/web3/parity.py index 6ff23cd09c..f660fa00e6 100644 --- a/web3/parity.py +++ b/web3/parity.py @@ -12,6 +12,7 @@ newAccount, sendTransaction, sign, + signTypedData, unlockAccount, ) from web3._utils.toolz import ( @@ -54,6 +55,7 @@ class ParityPersonal(ModuleV2): newAccount = newAccount sendTransaction = sendTransaction sign = sign + signTypedData = signTypedData unlockAccount = unlockAccount