Skip to content

Commit

Permalink
Merge pull request #1319 from Bhargavasomu/eip_712
Browse files Browse the repository at this point in the history
Add support for eth_signTypedData and personal_signTypedData RPC call
  • Loading branch information
kclowes authored May 1, 2019
2 parents 71b8bf0 + 4aaa1e3 commit 4075af0
Show file tree
Hide file tree
Showing 18 changed files with 362 additions and 8 deletions.
21 changes: 17 additions & 4 deletions docs/web3.eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,19 @@ The following methods are available on the ``web3.eth`` namespace.
'0x1a8bbe6eab8c72a219385681efefe565afd3accee35f516f8edf5ae82208fbd45a58f9f9116d8d88ba40fcd29076d6eada7027a3b412a9db55a0164547810cc401'
.. py:method:: Eth.signTypedData(account, jsonMessage)
* Delegates to ``eth_signTypedData`` RPC Method

Please note that the ``jsonMessage`` argument is the loaded JSON Object
and **NOT** the JSON String itself.

Signs the ``Structured Data`` (or ``Typed Data``) with the private key of the given ``account``.
The account must be unlocked.

``account`` may be a hex address or an ENS name


.. py:method:: Eth.call(transaction, block_identifier=web3.eth.defaultBlock)
* Delegates to ``eth_call`` RPC Method
Expand Down Expand Up @@ -864,8 +877,8 @@ with the filtering API.
* Delegates to ``eth_submitHashrate`` RPC Method

.. code-block:: python
>>> node_id = '59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c'
>>> node_id = '59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c'
>>> web3.eth.submitHashrate(5000, node_id)
True
Expand All @@ -875,14 +888,14 @@ with the filtering API.
* Delegates to ``eth_submitWork`` RPC Method.

.. code-block:: python
>>> web3.eth.submitWork(
1,
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
'0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000',
)
True
Contracts
---------
Expand Down
3 changes: 2 additions & 1 deletion docs/web3.geth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ The following methods are available on the ``web3.geth.personal`` namespace.
>>> web3.geth.personal.unlockAccount('0xd3cda913deb6f67967b99d67acdfa1712c293601', 'the-passphrase')
True
.. py:method:: sendTransaction(self, transaction, passphrase)
* Delegates to ``personal_sendTransaction`` RPC Method
Expand Down Expand Up @@ -502,7 +503,7 @@ Full documentation for Geth-supported endpoints can be found `here <https://gith
.. py:method:: Shh.newMessageFilter(self, criteria)
* Create a new filter id. This filter id can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.
* Create a new filter id. This filter id can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.

* Parameters:
* ``symKeyID``: When using symmetric key encryption, holds the symmetric key ID.
Expand Down
15 changes: 13 additions & 2 deletions docs/web3.parity.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,24 @@ The following methods are available on the ``web3.parity.personal`` namespace.
>>> web3.parity.personal.unlockAccount('0xd3cda913deb6f67967b99d67acdfa1712c293601', 'the-passphrase')
True
.. py:method:: sendTransaction(self, transaction, passphrase)
* Delegates to ``personal_sendTransaction`` RPC Method

Sends the transaction.


.. py:method:: signTypedData(self, jsonMessage, account, passphrase)
* Delegates to ``personal_signTypedData`` RPC Method

Please note that the ``jsonMessage`` argument is the loaded JSON Object
and **NOT** the JSON String itself.

Signs the ``Structured Data`` (or ``Typed Data``) with the passphrase of the given ``account``


ParityShh
---------

Expand Down Expand Up @@ -121,7 +132,7 @@ Full documentation for Parity-supported endpoints can be found `here <https://wi
.. py:method:: Shh.newMessageFilter(self, criteria)
* Return the filter ID that can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.
* Return the filter ID that can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.

* Parameters:
* ``decryptWith``: 32 bytes - Identity of key used for description. Null if listening for broadcasts.
Expand Down Expand Up @@ -176,7 +187,7 @@ Full documentation for Parity-supported endpoints can be found `here <https://wi
True

.. py:method:: Shh.unsubscribe(self, filter_id)
* Close a subscribed filter.

* Returns ``True`` if the filter subscription was sucesfully closed, otherwise ``False``
Expand Down
16 changes: 16 additions & 0 deletions tests/integration/go_ethereum/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ def test_eth_chainId(self, web3):
pytest.xfail('eth_chainId not implemented in geth 1.7.2')
super().test_eth_chainId(web3)

@pytest.mark.xfail(reason='eth_signTypedData has not been released in geth')
def test_eth_signTypedData(self,
web3,
unlocked_account_dual_type):
super().test_eth_signTypedData(
web3, unlocked_account_dual_type
)

@pytest.mark.xfail(reason='eth_signTypedData has not been released in geth')
def test_invalid_eth_signTypedData(self,
web3,
unlocked_account_dual_type):
super().test_invalid_eth_signTypedData(
web3, unlocked_account_dual_type
)


class GoEthereumVersionModuleTest(VersionModuleTest):
pass
Expand Down
16 changes: 16 additions & 0 deletions tests/integration/parity/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,22 @@ def test_eth_getLogs_without_logs(self, web3, block_with_txn_with_log):
result = web3.eth.getLogs(filter_params)
assert len(result) == 0

@pytest.mark.xfail(reason='eth_signTypedData has not been released in Parity')
def test_eth_signTypedData(self,
web3,
unlocked_account_dual_type):
super().test_eth_signTypedData(
web3, unlocked_account_dual_type
)

@pytest.mark.xfail(reason='eth_signTypedData has not been released in Parity')
def test_invalid_eth_signTypedData(self,
web3,
unlocked_account_dual_type):
super().test_invalid_eth_signTypedData(
web3, unlocked_account_dual_type
)


class ParityTraceModuleTest(TraceModuleTest):
pass
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/parity/test_parity_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def parity_command_arguments(
'--password', passwordfile,
'--jsonrpc-port', rpc_port,
'--jsonrpc-apis', 'all',
'--jsonrpc-experimental',
'--no-ipc',
'--no-ws',
'--whisper',
Expand All @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/parity/test_parity_ipc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def parity_command_arguments(
'--unlock', author,
'--password', passwordfile,
'--ipc-apis', 'all',
'--jsonrpc-experimental',
'--no-jsonrpc',
'--no-ws',
'--whisper',
Expand All @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/parity/test_parity_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def parity_command_arguments(
'--ws-port', ws_port,
'--ws-origins', '*',
'--ws-apis', 'all',
'--jsonrpc-experimental',
'--no-ipc',
'--no-jsonrpc',
'--whisper',
Expand All @@ -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',
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_ethereum_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def func_wrapper(self, eth_tester, *args, **kwargs):

class TestEthereumTesterEthModule(EthModuleTest):
test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError)
test_eth_signTypedData = not_implemented(EthModuleTest.test_eth_signTypedData, ValueError)
test_eth_signTransaction = not_implemented(EthModuleTest.test_eth_signTransaction, ValueError)
test_eth_submitHashrate = not_implemented(EthModuleTest.test_eth_submitHashrate, ValueError)
test_eth_submitWork = not_implemented(EthModuleTest.test_eth_submitWork, ValueError)
Expand Down
99 changes: 99 additions & 0 deletions web3/_utils/module_testing/eth_module.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

import json
import pytest

from eth_abi import (
Expand Down Expand Up @@ -202,6 +203,104 @@ def test_eth_sign(self, web3, unlocked_account_dual_type):
)
assert new_signature != signature

def test_eth_signTypedData(self, web3, unlocked_account_dual_type, skip_if_testrpc):
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!"
}
}
'''
skip_if_testrpc(web3)
signature = HexBytes(web3.eth.signTypedData(
unlocked_account_dual_type,
json.loads(validJSONMessage)
))
assert len(signature) == 32 + 32 + 1

def test_invalid_eth_signTypedData(self,
web3,
unlocked_account_dual_type,
skip_if_testrpc):
skip_if_testrpc(web3)
invalid_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[2]"},
{"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!"
}
}
'''
with pytest.raises(ValueError,
match=r".*Expected 2 items for array type Person\[2\], got 1 items.*"):
web3.eth.signTypedData(
unlocked_account_dual_type,
json.loads(invalid_typed_message)
)

def test_eth_signTransaction(self, web3, unlocked_account):
txn_params = {
'from': unlocked_account,
Expand Down
Loading

0 comments on commit 4075af0

Please sign in to comment.