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))), )
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}")
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 = Constants.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=Constants.contract_name_to_abi["Exchange"], ) 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}")
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." )