def is_valid_signature(provider: BaseProvider, data: str, signature: str,
                       signer_address: str) -> bool:
    """Check the validity of the supplied signature.
    Check if the supplied `signature`:code: corresponds to signing `data`:code:
    with the private key corresponding to `signer_address`:code:.
    :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.
    :returns: Tuple consisting of a boolean and a string.  Boolean is true if
        valid, false otherwise.  If false, the string describes the reason.
    >>> is_valid_signature(
    ...     Web3.HTTPProvider("http://127.0.0.1:8545"),
    ...     '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
    ...     '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
    ...     '0x5409ed021d9299bf6814279a6a1411a7e866a631',
    ... )
    True
    """  # noqa: E501 (line too long)
    assert_is_provider(provider, "provider")
    assert_is_hex_string(data, "data")
    assert_is_hex_string(signature, "signature")
    assert_is_address(signer_address, "signer_address")

    return Exchange(
        provider,
        chain_to_addresses(ChainId(1  # hard-coded to always be mainnet
                                   )).exchange,
    ).is_valid_hash_signature.call(
        bytes.fromhex(remove_0x_prefix(HexStr(data))),
        to_checksum_address(signer_address),
        bytes.fromhex(remove_0x_prefix(HexStr(signature))),
    )
Beispiel #2
0
def is_valid_signature(
    provider: BaseProvider, data: str, signature: str, signer_address: str
) -> Tuple[bool, str]:
    """Check the validity of the supplied signature.

    Check if the supplied `signature`:code: corresponds to signing `data`:code:
    with the private key corresponding to `signer_address`:code:.

    :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.
    :returns: Tuple consisting of a boolean and a string.  Boolean is true if
        valid, false otherwise.  If false, the string describes the reason.

    >>> is_valid_signature(
    ...     Web3.HTTPProvider("http://127.0.0.1:8545"),
    ...     '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0',
    ...     '0x1B61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403',
    ...     '0x5409ed021d9299bf6814279a6a1411a7e866a631',
    ... )
    (True, '')
    """  # noqa: E501 (line too long)
    assert_is_provider(provider, "provider")
    assert_is_hex_string(data, "data")
    assert_is_hex_string(signature, "signature")
    assert_is_address(signer_address, "signer_address")

    web3_instance = Web3(provider)
    # false positive from pylint: disable=no-member
    contract_address = NETWORK_TO_ADDRESSES[
        NetworkId(int(web3_instance.net.version))
    ].exchange
    # false positive from pylint: disable=no-member
    contract: Contract = web3_instance.eth.contract(
        address=to_checksum_address(contract_address),
        abi=zero_ex.contract_artifacts.abi_by_name("Exchange"),
    )
    try:
        return (
            contract.functions.isValidSignature(
                data, to_checksum_address(signer_address), signature
            ).call(),
            "",
        )
    except web3.exceptions.BadFunctionCallOutput as exception:
        known_revert_reasons = [
            "LENGTH_GREATER_THAN_0_REQUIRED",
            "SIGNATURE_ILLEGAL",
            "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}")
Beispiel #3
0
def generate_order_hash_hex(order: Order, exchange_address: str) -> str:
    """Calculate the hash of the given order as a hexadecimal string.

    :param order: The order to be hashed.  Must conform to `the 0x order JSON schema <https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json>`_.
    :param exchange_address: The address to which the 0x Exchange smart
        contract has been deployed.
    :returns: A string, of ASCII hex digits, representing the order hash.

    >>> generate_order_hash_hex(
    ...     {
    ...         'makerAddress': "0x0000000000000000000000000000000000000000",
    ...         'takerAddress': "0x0000000000000000000000000000000000000000",
    ...         'feeRecipientAddress': "0x0000000000000000000000000000000000000000",
    ...         'senderAddress': "0x0000000000000000000000000000000000000000",
    ...         'makerAssetAmount': "1000000000000000000",
    ...         'takerAssetAmount': "1000000000000000000",
    ...         'makerFee': "0",
    ...         'takerFee': "0",
    ...         'expirationTimeSeconds': "12345",
    ...         'salt': "12345",
    ...         'makerAssetData': (0).to_bytes(1, byteorder='big') * 20,
    ...         'takerAssetData': (0).to_bytes(1, byteorder='big') * 20,
    ...     },
    ...     exchange_address="0x0000000000000000000000000000000000000000",
    ... )
    '55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692'
    """  # noqa: E501 (line too long)
    assert_is_address(exchange_address, "exchange_address")
    assert_valid(order_to_jsdict(order, exchange_address), "/orderSchema")

    def pad_20_bytes_to_32(twenty_bytes: bytes):
        return bytes(12) + twenty_bytes

    def int_to_32_big_endian_bytes(i: int):
        return i.to_bytes(32, byteorder="big")

    eip712_domain_struct_hash = keccak(
        _Constants.eip712_domain_struct_header +
        pad_20_bytes_to_32(to_bytes(hexstr=exchange_address)))

    eip712_order_struct_hash = keccak(
        _Constants.eip712_order_schema_hash +
        pad_20_bytes_to_32(to_bytes(hexstr=order["makerAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["takerAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["feeRecipientAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["senderAddress"])) +
        int_to_32_big_endian_bytes(int(order["makerAssetAmount"])) +
        int_to_32_big_endian_bytes(int(order["takerAssetAmount"])) +
        int_to_32_big_endian_bytes(int(order["makerFee"])) +
        int_to_32_big_endian_bytes(int(order["takerFee"])) +
        int_to_32_big_endian_bytes(int(order["expirationTimeSeconds"])) +
        int_to_32_big_endian_bytes(int(order["salt"])) +
        keccak(to_bytes(hexstr=order["makerAssetData"].hex())) +
        keccak(to_bytes(hexstr=order["takerAssetData"].hex())))

    return keccak(_Constants.eip191_header + eip712_domain_struct_hash +
                  eip712_order_struct_hash).hex()
Beispiel #4
0
def sign_hash(
    provider: BaseProvider, signer_address: str, hash_hex: str
) -> str:
    """Sign a message with the given hash, and return the signature.

    :param provider: A Web3 provider.
    :param signer_address: The address of the signing account.
    :param hash_hex: A hex string representing the hash, like that returned
        from `generate_order_hash_hex()`:code:.
    :returns: A string, of ASCII hex digits, representing the signature.

    >>> provider = Web3.HTTPProvider("http://127.0.0.1:8545")
    >>> sign_hash(
    ...     provider,
    ...     Web3(provider).geth.personal.listAccounts()[0],
    ...     '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004',
    ... )
    '0x1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03'
    """  # noqa: E501 (line too long)
    assert_is_provider(provider, "provider")
    assert_is_address(signer_address, "signer_address")
    assert_is_hex_string(hash_hex, "hash_hex")

    web3_instance = Web3(provider)
    # false positive from pylint: disable=no-member
    signature = web3_instance.eth.sign(  # type: ignore
        signer_address, hexstr=hash_hex.replace("0x", "")
    ).hex()

    valid_v_param_values = [27, 28]

    # HACK: There is no consensus on whether the signatureHex string should be
    # formatted as v + r + s OR r + s + v, and different clients (even
    # different versions of the same client) return the signature params in
    # different orders. In order to support all client implementations, we
    # parse the signature in both ways, and evaluate if either one is a valid
    # signature.  r + s + v is the most prevalent format from eth_sign, so we
    # attempt this first.

    ec_signature = _parse_signature_hex_as_rsv(signature)
    if ec_signature["v"] in valid_v_param_values:
        signature_as_vrst_hex = (
            _convert_ec_signature_to_vrs_hex(ec_signature)
            + _Constants.SignatureType.ETH_SIGN.value.to_bytes(
                1, byteorder="big"
            ).hex()
        )

        (valid, _) = is_valid_signature(
            provider, hash_hex, signature_as_vrst_hex, signer_address
        )

        if valid is True:
            return signature_as_vrst_hex

    ec_signature = _parse_signature_hex_as_vrs(signature)
    if ec_signature["v"] in valid_v_param_values:
        signature_as_vrst_hex = (
            _convert_ec_signature_to_vrs_hex(ec_signature)
            + _Constants.SignatureType.ETH_SIGN.value.to_bytes(
                1, byteorder="big"
            ).hex()
        )
        (valid, _) = is_valid_signature(
            provider, hash_hex, signature_as_vrst_hex, signer_address
        )

        if valid is True:
            return signature_as_vrst_hex

    raise RuntimeError(
        "Signature returned from web3 provider is in an unknown format."
        + " Attempted to parse as RSV and as VRS."
    )
Beispiel #5
0
def generate_order_hash_hex(order: Order, exchange_address: str,
                            chain_id: int) -> str:
    """Calculate the hash of the given order as a hexadecimal string.

    :param order: The order to be hashed.  Must conform to `the 0x order JSON schema <https://github.com/0xProject/0x-monorepo/blob/development/packages/json-schemas/schemas/order_schema.json>`_.
    :param exchange_address: The address to which the 0x Exchange smart
        contract has been deployed.
    :returns: A string, of ASCII hex digits, representing the order hash.

    Inputs and expected result below were copied from
    @0x/order-utils/test/order_hash_test.ts

    >>> generate_order_hash_hex(
    ...     Order(
    ...         makerAddress="0x0000000000000000000000000000000000000000",
    ...         takerAddress="0x0000000000000000000000000000000000000000",
    ...         feeRecipientAddress="0x0000000000000000000000000000000000000000",
    ...         senderAddress="0x0000000000000000000000000000000000000000",
    ...         makerAssetAmount="0",
    ...         takerAssetAmount="0",
    ...         makerFee="0",
    ...         takerFee="0",
    ...         expirationTimeSeconds="0",
    ...         salt="0",
    ...         makerAssetData=((0).to_bytes(1, byteorder='big') * 20),
    ...         takerAssetData=((0).to_bytes(1, byteorder='big') * 20),
    ...         makerFeeAssetData=((0).to_bytes(1, byteorder='big') * 20),
    ...         takerFeeAssetData=((0).to_bytes(1, byteorder='big') * 20),
    ...     ),
    ...     exchange_address="0x1dc4c1cefef38a777b15aa20260a54e584b16c48",
    ...     chain_id=1337
    ... )
    'cb36e4fedb36508fb707e2c05e21bffc7a72766ccae93f8ff096693fff7f1714'
    """  # noqa: E501 (line too long)
    assert_is_address(exchange_address, "exchange_address")
    assert_valid(order_to_jsdict(order, chain_id, exchange_address),
                 "/orderSchema")

    def pad_20_bytes_to_32(twenty_bytes: bytes):
        return bytes(12) + twenty_bytes

    def int_to_32_big_endian_bytes(i: int):
        return i.to_bytes(32, byteorder="big")

    eip712_domain_struct_hash = keccak(
        _Constants.eip712_domain_struct_header +
        int_to_32_big_endian_bytes(int(chain_id)) +
        pad_20_bytes_to_32(to_bytes(hexstr=exchange_address)))

    def ensure_bytes(str_or_bytes: Union[str, bytes]) -> bytes:
        return (to_bytes(hexstr=cast(bytes, str_or_bytes)) if isinstance(
            str_or_bytes, str) else str_or_bytes)

    eip712_order_struct_hash = keccak(
        _Constants.eip712_order_schema_hash +
        pad_20_bytes_to_32(to_bytes(hexstr=order["makerAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["takerAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["feeRecipientAddress"])) +
        pad_20_bytes_to_32(to_bytes(hexstr=order["senderAddress"])) +
        int_to_32_big_endian_bytes(int(order["makerAssetAmount"])) +
        int_to_32_big_endian_bytes(int(order["takerAssetAmount"])) +
        int_to_32_big_endian_bytes(int(order["makerFee"])) +
        int_to_32_big_endian_bytes(int(order["takerFee"])) +
        int_to_32_big_endian_bytes(int(order["expirationTimeSeconds"])) +
        int_to_32_big_endian_bytes(int(order["salt"])) +
        keccak(ensure_bytes(order["makerAssetData"])) +
        keccak(ensure_bytes(order["takerAssetData"])) +
        keccak(ensure_bytes(order["makerFeeAssetData"])) +
        keccak(ensure_bytes(order["takerFeeAssetData"])))

    return keccak(_Constants.eip191_header + eip712_domain_struct_hash +
                  eip712_order_struct_hash).hex()