Skip to content

Commit 101b6a8

Browse files
committed
Add support for eth_signTypedData, personal_signTypedData RPC call
1 parent cba9538 commit 101b6a8

File tree

18 files changed

+377
-10
lines changed

18 files changed

+377
-10
lines changed

docs/web3.eth.rst

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,19 @@ The following methods are available on the ``web3.eth`` namespace.
654654
'0x1a8bbe6eab8c72a219385681efefe565afd3accee35f516f8edf5ae82208fbd45a58f9f9116d8d88ba40fcd29076d6eada7027a3b412a9db55a0164547810cc401'
655655
656656
657+
.. py:method:: Eth.signTypedData(account, jsonMessage)
658+
659+
* Delegates to ``eth_signTypedData`` RPC Method
660+
661+
Please note that the ``jsonMessage`` argument is the loaded JSON Object
662+
and **NOT** the JSON String itself.
663+
664+
Signs the ``Structured Data`` (or ``Typed Data``) with the private key of the given ``account``.
665+
The account must be unlocked.
666+
667+
``account`` may be a hex address or an ENS name
668+
669+
657670
.. py:method:: Eth.call(transaction, block_identifier=web3.eth.defaultBlock)
658671
659672
* Delegates to ``eth_call`` RPC Method
@@ -864,8 +877,8 @@ with the filtering API.
864877
* Delegates to ``eth_submitHashrate`` RPC Method
865878

866879
.. code-block:: python
867-
868-
>>> node_id = '59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c'
880+
881+
>>> node_id = '59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c'
869882
>>> web3.eth.submitHashrate(5000, node_id)
870883
True
871884
@@ -875,14 +888,14 @@ with the filtering API.
875888
* Delegates to ``eth_submitWork`` RPC Method.
876889

877890
.. code-block:: python
878-
891+
879892
>>> web3.eth.submitWork(
880893
1,
881894
'0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
882895
'0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000',
883896
)
884897
True
885-
898+
886899
887900
Contracts
888901
---------

docs/web3.geth.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,24 @@ The following methods are available on the ``web3.geth.personal`` namespace.
251251
>>> web3.geth.personal.unlockAccount('0xd3cda913deb6f67967b99d67acdfa1712c293601', 'the-passphrase')
252252
True
253253
254+
254255
.. py:method:: sendTransaction(self, transaction, passphrase)
255256
256257
* Delegates to ``personal_sendTransaction`` RPC Method
257258

258259
Sends the transaction.
259260

260261

262+
.. py:method:: signTypedData(self, jsonMessage, account, passphrase)
263+
264+
* Delegates to ``personal_signTypedData`` RPC Method
265+
266+
Please note that the ``jsonMessage`` argument is the loaded JSON Object
267+
and **NOT** the JSON String itself.
268+
269+
Signs the ``Structured Data`` (or ``Typed Data``) with the passphrase of the given ``account``
270+
271+
261272
.. py:module:: web3.geth.txpool
262273
263274
GethTxPool API
@@ -502,7 +513,7 @@ Full documentation for Geth-supported endpoints can be found `here <https://gith
502513
503514
.. py:method:: Shh.newMessageFilter(self, criteria)
504515
505-
* Create a new filter id. This filter id can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.
516+
* Create a new filter id. This filter id can be used with ``ShhFilter`` to poll for new messages that match the set of criteria.
506517

507518
* Parameters:
508519
* ``symKeyID``: When using symmetric key encryption, holds the symmetric key ID.

docs/web3.parity.rst

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,24 @@ The following methods are available on the ``web3.parity.personal`` namespace.
6464
>>> web3.parity.personal.unlockAccount('0xd3cda913deb6f67967b99d67acdfa1712c293601', 'the-passphrase')
6565
True
6666
67+
6768
.. py:method:: sendTransaction(self, transaction, passphrase)
6869
6970
* Delegates to ``personal_sendTransaction`` RPC Method
7071

7172
Sends the transaction.
7273

7374

75+
.. py:method:: signTypedData(self, jsonMessage, account, passphrase)
76+
77+
* Delegates to ``personal_signTypedData`` RPC Method
78+
79+
Please note that the ``jsonMessage`` argument is the loaded JSON Object
80+
and **NOT** the JSON String itself.
81+
82+
Signs the ``Structured Data`` (or ``Typed Data``) with the passphrase of the given ``account``
83+
84+
7485
ParityShh
7586
---------
7687

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

126137
* Parameters:
127138
* ``decryptWith``: 32 bytes - Identity of key used for description. Null if listening for broadcasts.
128139
* ``from``: 64 bytes - If present, only accept messages signed by this key.
129140
* ``topics``: Array of possible topics (or partial topics). Should be non-empty.
130-
131-
* Returns the newly created filter id.
141+
142+
* Returns the newly created filter id.
132143

133144
.. code-block:: python
134145
@@ -176,7 +187,7 @@ Full documentation for Parity-supported endpoints can be found `here <https://wi
176187
True
177188

178189
.. py:method:: Shh.unsubscribe(self, filter_id)
179-
190+
180191
* Close a subscribed filter.
181192

182193
* Returns ``True`` if the filter subscription was sucesfully closed, otherwise ``False``

tests/integration/go_ethereum/common.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ def test_eth_chainId(self, web3):
7171
pytest.xfail('eth_chainId not implemented in geth 1.7.2')
7272
super().test_eth_chainId(web3)
7373

74+
def test_eth_signTypedData(self,
75+
web3,
76+
unlocked_account_dual_type):
77+
pytest.xfail('eth_signTypedData JSON RPC call has not been released in geth')
78+
super().test_eth_signTypedData(
79+
web3, unlocked_account_dual_type
80+
)
81+
82+
def test_invalid_eth_signTypedData(self,
83+
web3,
84+
unlocked_account_dual_type):
85+
pytest.xfail('eth_signTypedData JSON RPC call has not been released in geth')
86+
super().test_invalid_eth_signTypedData(
87+
web3, unlocked_account_dual_type
88+
)
89+
7490

7591
class GoEthereumVersionModuleTest(VersionModuleTest):
7692
pass

tests/integration/parity/common.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,22 @@ def test_eth_getLogs_without_logs(self, web3, block_with_txn_with_log):
168168
result = web3.eth.getLogs(filter_params)
169169
assert len(result) == 0
170170

171+
def test_eth_signTypedData(self,
172+
web3,
173+
unlocked_account_dual_type):
174+
pytest.xfail('eth_signTypedData JSON RPC call has not been released in parity')
175+
super().test_eth_signTypedData(
176+
web3, unlocked_account_dual_type
177+
)
178+
179+
def test_invalid_eth_signTypedData(self,
180+
web3,
181+
unlocked_account_dual_type):
182+
pytest.xfail('eth_signTypedData JSON RPC call has not been released in parity')
183+
super().test_invalid_eth_signTypedData(
184+
web3, unlocked_account_dual_type
185+
)
186+
171187

172188
class ParityTraceModuleTest(TraceModuleTest):
173189
pass

tests/integration/parity/test_parity_http.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def parity_command_arguments(
4949
'--password', passwordfile,
5050
'--jsonrpc-port', rpc_port,
5151
'--jsonrpc-apis', 'all',
52+
'--jsonrpc-experimental',
5253
'--no-ipc',
5354
'--no-ws',
5455
'--whisper',
@@ -65,6 +66,7 @@ def parity_import_blocks_command(parity_binary, rpc_port, datadir, passwordfile)
6566
'--password', passwordfile,
6667
'--jsonrpc-port', str(rpc_port),
6768
'--jsonrpc-apis', 'all',
69+
'--jsonrpc-experimental',
6870
'--no-ipc',
6971
'--no-ws',
7072
'--tracing', 'on',

tests/integration/parity/test_parity_ipc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ def parity_command_arguments(
4747
'--unlock', author,
4848
'--password', passwordfile,
4949
'--ipc-apis', 'all',
50+
'--jsonrpc-experimental',
5051
'--no-jsonrpc',
5152
'--no-ws',
5253
'--whisper',
@@ -63,6 +64,7 @@ def parity_import_blocks_command(parity_binary, ipc_path, datadir, passwordfile)
6364
'--base-path', datadir,
6465
'--password', passwordfile,
6566
'--ipc-apis', 'all',
67+
'--jsonrpc-experimental',
6668
'--no-jsonrpc',
6769
'--no-ws',
6870
'--tracing', 'on',

tests/integration/parity/test_parity_ws.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def parity_command_arguments(
5151
'--ws-port', ws_port,
5252
'--ws-origins', '*',
5353
'--ws-apis', 'all',
54+
'--jsonrpc-experimental',
5455
'--no-ipc',
5556
'--no-jsonrpc',
5657
'--whisper',
@@ -68,6 +69,7 @@ def parity_import_blocks_command(parity_binary, ws_port, datadir, passwordfile):
6869
'--ws-port', str(ws_port),
6970
'--ws-origins', '*',
7071
'--ws-apis', 'all',
72+
'--jsonrpc-experimental',
7173
'--no-ipc',
7274
'--no-jsonrpc',
7375
'--tracing', 'on',

tests/integration/test_ethereum_tester.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ def func_wrapper(self, eth_tester, *args, **kwargs):
220220

221221
class TestEthereumTesterEthModule(EthModuleTest):
222222
test_eth_sign = not_implemented(EthModuleTest.test_eth_sign, ValueError)
223+
test_eth_signTypedData = not_implemented(EthModuleTest.test_eth_signTypedData, ValueError)
223224
test_eth_signTransaction = not_implemented(EthModuleTest.test_eth_signTransaction, ValueError)
224225
test_eth_submitHashrate = not_implemented(EthModuleTest.test_eth_submitHashrate, ValueError)
225226
test_eth_submitWork = not_implemented(EthModuleTest.test_eth_submitWork, ValueError)

web3/_utils/module_testing/eth_module.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3+
import json
34
import pytest
45

56
from eth_abi import (
@@ -202,6 +203,107 @@ def test_eth_sign(self, web3, unlocked_account_dual_type):
202203
)
203204
assert new_signature != signature
204205

206+
def test_eth_signTypedData(self, web3, unlocked_account_dual_type):
207+
validJSONMessage = '''
208+
{
209+
"types": {
210+
"EIP712Domain": [
211+
{"name": "name", "type": "string"},
212+
{"name": "version", "type": "string"},
213+
{"name": "chainId", "type": "uint256"},
214+
{"name": "verifyingContract", "type": "address"}
215+
],
216+
"Person": [
217+
{"name": "name", "type": "string"},
218+
{"name": "wallet", "type": "address"}
219+
],
220+
"Mail": [
221+
{"name": "from", "type": "Person"},
222+
{"name": "to", "type": "Person"},
223+
{"name": "contents", "type": "string"}
224+
]
225+
},
226+
"primaryType": "Mail",
227+
"domain": {
228+
"name": "Ether Mail",
229+
"version": "1",
230+
"chainId": "0x01",
231+
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
232+
},
233+
"message": {
234+
"from": {
235+
"name": "Cow",
236+
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
237+
},
238+
"to": {
239+
"name": "Bob",
240+
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
241+
},
242+
"contents": "Hello, Bob!"
243+
}
244+
}
245+
'''
246+
signature = HexBytes(web3.eth.signTypedData(
247+
unlocked_account_dual_type,
248+
json.loads(validJSONMessage)
249+
))
250+
assert len(signature) == 32 + 32 + 1
251+
252+
def test_invalid_eth_signTypedData(self,
253+
web3,
254+
unlocked_account_dual_type):
255+
invalid_typed_message = '''
256+
{
257+
"types": {
258+
"EIP712Domain": [
259+
{"name": "name", "type": "string"},
260+
{"name": "version", "type": "string"},
261+
{"name": "chainId", "type": "uint256"},
262+
{"name": "verifyingContract", "type": "address"}
263+
],
264+
"Person": [
265+
{"name": "name", "type": "string"},
266+
{"name": "wallet", "type": "address"}
267+
],
268+
"Mail": [
269+
{"name": "from", "type": "Person"},
270+
{"name": "to", "type": "Person[2]"},
271+
{"name": "contents", "type": "string"}
272+
]
273+
},
274+
"primaryType": "Mail",
275+
"domain": {
276+
"name": "Ether Mail",
277+
"version": "1",
278+
"chainId": "0x01",
279+
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
280+
},
281+
"message": {
282+
"from": {
283+
"name": "Cow",
284+
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
285+
},
286+
"to": [{
287+
"name": "Bob",
288+
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
289+
}],
290+
"contents": "Hello, Bob!"
291+
}
292+
}
293+
'''
294+
# ValueError is caused by the error in the response which is as follows
295+
# ``Expected 2 items for array type Person[2], got 1 items``
296+
with pytest.raises(ValueError) as e:
297+
web3.eth.signTypedData(
298+
unlocked_account_dual_type,
299+
json.loads(invalid_typed_message)
300+
)
301+
try:
302+
assert "Expected 2 items for array type Person[2], got 1 items" in str(e.value)
303+
except AssertionError:
304+
# This error is raised by eth-tester
305+
assert "Unknown RPC Endpoint: eth_signTypedData" in str(e.value)
306+
205307
def test_eth_signTransaction(self, web3, unlocked_account):
206308
txn_params = {
207309
'from': unlocked_account,

0 commit comments

Comments
 (0)