aboutsummaryrefslogtreecommitdiffstats
path: root/python-packages/order_utils/src
diff options
context:
space:
mode:
authorF. Eugene Aumson <feuGeneA@users.noreply.github.com>2018-11-08 00:20:46 +0800
committerGitHub <noreply@github.com>2018-11-08 00:20:46 +0800
commit95b2898b9c0898c7e2d98ee603bff0604bf2a829 (patch)
tree6ac589bc869b0e177b48e4a2ae545fc986b08cba /python-packages/order_utils/src
parent094f71066294d14bc1920e4d3ddf4e7705201d61 (diff)
downloaddexon-sol-tools-95b2898b9c0898c7e2d98ee603bff0604bf2a829.tar.gz
dexon-sol-tools-95b2898b9c0898c7e2d98ee603bff0604bf2a829.tar.zst
dexon-sol-tools-95b2898b9c0898c7e2d98ee603bff0604bf2a829.zip
[order_utils.py] is_signature_valid, via Exchange contract (#1216)
First support for signature validation, done via Exchange contract's isValidSignature() method.
Diffstat (limited to 'python-packages/order_utils/src')
-rw-r--r--python-packages/order_utils/src/conf.py3
-rw-r--r--python-packages/order_utils/src/index.rst3
-rw-r--r--python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py1
l---------python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts1
-rw-r--r--python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py10
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/__init__.py12
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/py.typed (renamed from python-packages/order_utils/src/zero_ex/py.typed)0
-rw-r--r--python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py88
8 files changed, 116 insertions, 2 deletions
diff --git a/python-packages/order_utils/src/conf.py b/python-packages/order_utils/src/conf.py
index 606cd3b2a..6b6776d01 100644
--- a/python-packages/order_utils/src/conf.py
+++ b/python-packages/order_utils/src/conf.py
@@ -3,6 +3,7 @@
# Reference: http://www.sphinx-doc.org/en/master/config
from typing import List
+import pkg_resources
# pylint: disable=invalid-name
@@ -12,7 +13,7 @@ project = "0x-order-utils"
# pylint: disable=redefined-builtin
copyright = "2018, ZeroEx, Intl."
author = "F. Eugene Aumson"
-version = "0.1.0" # The short X.Y version
+version = pkg_resources.get_distribution("0x-order-utils").version
release = "" # The full version, including alpha/beta/rc tags
extensions = [
diff --git a/python-packages/order_utils/src/index.rst b/python-packages/order_utils/src/index.rst
index 22d5b0ef9..b99addabd 100644
--- a/python-packages/order_utils/src/index.rst
+++ b/python-packages/order_utils/src/index.rst
@@ -19,6 +19,9 @@ Python zero_ex.order_utils
See source for class properties. Sphinx does not easily generate class property docs; pull requests welcome.
+.. automodule:: zero_ex.order_utils.signature_utils
+ :members:
+
Indices and tables
==================
diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py b/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py
new file mode 100644
index 000000000..ed45d2c8e
--- /dev/null
+++ b/python-packages/order_utils/src/zero_ex/contract_artifacts/__init__.py
@@ -0,0 +1 @@
+"""Solc-generated artifacts for 0x smart contracts."""
diff --git a/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts b/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
new file mode 120000
index 000000000..82d28ba87
--- /dev/null
+++ b/python-packages/order_utils/src/zero_ex/contract_artifacts/artifacts
@@ -0,0 +1 @@
+../../../../../packages/contract-artifacts/artifacts \ No newline at end of file
diff --git a/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py
index a100da567..08c1b0ea5 100644
--- a/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py
+++ b/python-packages/order_utils/src/zero_ex/dev_utils/type_assertions.py
@@ -46,3 +46,13 @@ def assert_is_int(value: Any, name: str) -> None:
f"expected variable '{name}', with value {str(value)}, to have"
+ f" type 'int', not '{type(value).__name__}'"
)
+
+
+def assert_is_hex_string(value: Any, name: str) -> None:
+ """Assert that :param value: is a string of hex chars.
+
+ If :param value: isn't a str, raise a TypeError. If it is a string but
+ contains non-hex characters ("0x" prefix permitted), raise a ValueError.
+ """
+ assert_is_string(value, name)
+ int(value, 16) # raises a ValueError if value isn't a base-16 str
diff --git a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
index f014af0f6..80445cb6e 100644
--- a/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
+++ b/python-packages/order_utils/src/zero_ex/order_utils/__init__.py
@@ -1 +1,11 @@
-"""Order utilities for 0x applications."""
+"""Order utilities for 0x applications.
+
+Some methods require the caller to pass in a `Web3.HTTPProvider` object. For
+local testing one may construct such a provider pointing at an instance of
+`ganache-cli <https://www.npmjs.com/package/ganache-cli>`_ which has the 0x
+contracts deployed on it. For convenience, a docker container is provided for
+just this purpose. To start it: ``docker run -d -p 8545:8545 0xorg/ganache-cli
+--gasLimit 10000000 --db /snapshot --noVMErrorsOnRPCResponse -p 8545
+--networkId 50 -m "concert load couple harbor equip island argue ramp clarify
+fence smart topic"``.
+"""
diff --git a/python-packages/order_utils/src/zero_ex/py.typed b/python-packages/order_utils/src/zero_ex/order_utils/py.typed
index e69de29bb..e69de29bb 100644
--- a/python-packages/order_utils/src/zero_ex/py.typed
+++ b/python-packages/order_utils/src/zero_ex/order_utils/py.typed
diff --git a/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py
new file mode 100644
index 000000000..12525ba88
--- /dev/null
+++ b/python-packages/order_utils/src/zero_ex/order_utils/signature_utils.py
@@ -0,0 +1,88 @@
+"""Signature utilities."""
+
+from typing import Dict, Tuple
+import json
+from pkg_resources import resource_string
+
+from eth_utils import is_address, to_checksum_address
+from web3 import Web3
+import web3.exceptions
+from web3.utils import datatypes
+
+from zero_ex.dev_utils.type_assertions import assert_is_hex_string
+
+
+# prefer `black` formatting. pylint: disable=C0330
+EXCHANGE_ABI = json.loads(
+ resource_string("zero_ex.contract_artifacts", "artifacts/Exchange.json")
+)["compilerOutput"]["abi"]
+
+network_to_exchange_addr: Dict[str, str] = {
+ "1": "0x4f833a24e1f95d70f028921e27040ca56e09ab0b",
+ "3": "0x4530c0483a1633c7a1c97d2c53721caff2caaaaf",
+ "42": "0x35dd2932454449b14cee11a94d3674a936d5d7b2",
+ "50": "0x48bacb9266a570d521063ef5dd96e61686dbe788",
+}
+
+
+# prefer `black` formatting. pylint: disable=C0330
+def is_valid_signature(
+ provider: Web3.HTTPProvider, data: str, signature: str, signer_address: str
+) -> Tuple[bool, str]:
+ # docstring considered all one line by pylint: disable=line-too-long
+ """Check the validity of the supplied signature.
+
+ Check if the supplied ``signature`` corresponds to signing ``data`` with
+ the private key corresponding to ``signer_address``.
+
+ :param provider: A Web3 provider able to access the 0x Exchange contract.
+ :param data: The hex encoded data signed by the supplied signature.
+ :param signature: The hex encoded signature.
+ :param signer_address: The hex encoded address that signed the data to
+ produce the supplied signature.
+ :rtype: Boolean indicating whether the given signature is valid.
+
+ >>> is_valid_signature(
+ ... Web3.HTTPProvider("http://127.0.0.1:8545"),
+ ... '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
+ ... '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
+ ... '0x5409ed021d9299bf6814279a6a1411a7e866a631',
+ ... )
+ (True, '')
+ """ # noqa: E501 (line too long)
+ # TODO: make this provider check more flexible. pylint: disable=fixme
+ # https://app.asana.com/0/684263176955174/901300863045491/f
+ if not isinstance(provider, Web3.HTTPProvider):
+ raise TypeError("provider is not a Web3.HTTPProvider")
+ assert_is_hex_string(data, "data")
+ assert_is_hex_string(signature, "signature")
+ assert_is_hex_string(signer_address, "signer_address")
+ if not is_address(signer_address):
+ raise ValueError("signer_address is not a valid address")
+
+ web3_instance = Web3(provider)
+ # false positive from pylint: disable=no-member
+ network_id = web3_instance.net.version
+ contract_address = network_to_exchange_addr[network_id]
+ # false positive from pylint: disable=no-member
+ contract: datatypes.Contract = web3_instance.eth.contract(
+ address=to_checksum_address(contract_address), abi=EXCHANGE_ABI
+ )
+ try:
+ return (
+ contract.call().isValidSignature(
+ data, to_checksum_address(signer_address), signature
+ ),
+ "",
+ )
+ except web3.exceptions.BadFunctionCallOutput as exception:
+ known_revert_reasons = [
+ "LENGTH_GREATER_THAN_0_REQUIRED",
+ "SIGNATURE_UNSUPPORTED",
+ "LENGTH_0_REQUIRED",
+ "LENGTH_65_REQUIRED",
+ ]
+ for known_revert_reason in known_revert_reasons:
+ if known_revert_reason in str(exception):
+ return (False, known_revert_reason)
+ return (False, f"Unknown: {exception}")