def process_sign_typeddata(self, data_bin): """Process a WalletConnect eth_signTypedData call""" data_obj = json.loads(data_bin) chain_id = None if "domain" in data_obj and "chainId" in data_obj["domain"]: chain_id = data_obj["domain"]["chainId"] if isinstance(chain_id, str) and chain_id.startswith("eip155:"): chain_id = int(chain_id[7:]) # Silent ignore when chain ids mismatch if chain_id is not None and self.chainID != data_obj["domain"]["chainId"]: logger.debug("Wrong chain id in signedTypedData") return None hash_domain, hash_data = typed_sign_hash(data_obj) sign_request = ( "WalletConnect signature request :\n\n" f"- Data to sign (typed) :\n" f"{print_text_query(data_obj)}" f"\n - Hash domain (hex) :\n" f" 0x{hash_domain.hex().upper()}\n" f"\n - Hash data (hex) :\n" f" 0x{hash_data.hex().upper()}\n" ) if self.current_device.has_screen: sign_request += USER_SCREEN elif self.current_device.has_hardware_button: sign_request += USER_BUTTON if self.confirm_callback(sign_request): if self.current_device.has_screen: v, r, s = self.current_device.sign_eip712(hash_domain, hash_data) return self.eth.encode_vrs(v, r, s) # else hash_sign = sha3(EIP712_HEADER + hash_domain + hash_data) der_signature = self.current_device.sign(hash_sign) return self.eth.encode_datasign(hash_sign, der_signature)
def process_sign_message(self, data_hex): """Process a WalletConnect personal_sign and eth_sign call""" # sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))) data_bin = bytes.fromhex(data_hex[2:]) msg_header = MESSAGE_HEADER + str(len(data_bin)).encode("ascii") sign_request = ( "WalletConnect signature request :\n\n" f"- Data to sign (hex) :\n" f"- {data_hex}\n" f"\n Data to sign (ASCII/UTF8) :\n" ) try: sign_request += f" {data_bin.decode('utf8')}\n" except UnicodeDecodeError: sign_request += " <can't decode sign data to text>" if self.current_device.has_screen: hash2_data = sha2(data_bin).hex().upper() sign_request += f"\n Hash data to sign (hex) :\n {hash2_data}\n" sign_request += USER_SCREEN elif self.current_device.has_hardware_button: sign_request += USER_BUTTON if self.confirm_callback(sign_request): if self.current_device.has_screen: v, r, s = self.current_device.sign_message(data_bin) return self.eth.encode_vrs(v, r, s) # else hash_sign = sha3(msg_header + data_bin) der_signature = self.current_device.sign(hash_sign) return self.eth.encode_datasign(hash_sign, der_signature)
def __init__(self, pubkey, network, api, chainID, ERC20=None): self.pubkey = pubkey key_hash = sha3(self.pubkey[1:]) self.address = format_checksum_address(key_hash.hex()[-40:]) self.ERC20 = ERC20 self.api = api self.decimals = self.get_decimals() self.token_symbol = self.get_symbol() self.chainID = chainID
def format_checksum_address(addr): # Format an ETH address with checksum (without 0x) addr = addr.lower() addr_sha3hash = sha3(addr.encode("ascii")).hex() cs_address = "" for idx, ci in enumerate(addr): if ci in "abcdef": cs_address += ci.upper() if int(addr_sha3hash[idx], 16) >= 8 else ci continue cs_address += ci return cs_address
def prepare(self, toaddr, paymentvalue, gprice, glimit, data=bytearray(b"")): """Build a transaction to be signed. toaddr in hex without 0x value in wei, gprice in Wei """ if self.ERC20: maxspendable = self.getbalance(False) balance_eth = self.getbalance() if balance_eth < (gprice * glimit): raise NotEnoughTokens("Not enough native ETH funding for the tx fee") else: maxspendable = self.getbalance() - (gprice * glimit) if paymentvalue > maxspendable or paymentvalue < 0: if self.ERC20: sym = self.token_symbol else: sym = "native ETH" raise NotEnoughTokens(f"Not enough {sym} tokens for the tx") self.nonce = int2bytearray(self.getnonce()) self.gasprice = int2bytearray(gprice) self.startgas = int2bytearray(glimit) if self.ERC20: self.to = bytearray.fromhex(self.ERC20[2:]) self.value = int2bytearray(int(0)) self.data = bytearray.fromhex(TRANSFERT_FUNCTION + "00" * 12 + toaddr) + uint256( paymentvalue ) else: self.to = bytearray.fromhex(toaddr) self.value = int2bytearray(int(paymentvalue)) self.data = data v = int2bytearray(self.chainID) r = int2bytearray(0) s = int2bytearray(0) signing_tx = rlp_encode( [ self.nonce, self.gasprice, self.startgas, self.to, self.value, self.data, v, r, s, ] ) self.datahash = sha3(signing_tx) return (signing_tx, self.datahash)
def full_message_digest(domain_hash, digest_data): return sha3(b"\x19\x01" + domain_hash + digest_data)
def encode_value(vtype, value, go): """Encode a given value in Python bytes.""" if vtype == "bool": if not isinstance(value, bool): raise ValueError("bool type is not a bool value.") return uint256(1) if value else uint256(0) if vtype == "address": if not isinstance(value, str): raise ValueError("address type is not a str value.") if len(value) != 42 or value[:2] != "0x": raise ValueError("address is not a 0x hex value.") try: int_value = int(value[2:], 16) except ValueError: raise ValueError("address is not a 0x hex value.") return uint256(int_value) if vtype in int_types or vtype in uint_types: if isinstance(value, str): # Fallback for dapp encoding uint as string try: value = int(value, 10) except ValueError: raise ValueError(vtype + " is not a valid string.") if not isinstance(value, int): raise ValueError(vtype + " type is not a int nor str value.") if value >= 0: intval_bin = uint256(value) else: intval_bin = uint256(2**256 + value) return intval_bin if vtype in bytes_types: if not isinstance(value, str): raise ValueError("bytes type is not a str value.") if value[:2] != "0x": raise ValueError("bytes is not a 0x hex value.") try: out = bytes.fromhex(value[2:]) except ValueError: raise ValueError("bytes is not a 0x hex value.") while len(out) < 32: out += b"\0" return out if vtype == "bytes": if not isinstance(value, str): raise ValueError("bytes type is not a str value.") if value[:2] != "0x": raise ValueError("bytes is not a 0x hex value.") try: out = bytes.fromhex(value[2:]) except ValueError: raise ValueError("bytes is not a 0x hex value.") return sha3(out) if vtype == "string": if not isinstance(value, str): raise ValueError("string type is not a str value.") return sha3(value.encode("utf8")) if vtype.endswith("[]"): if not isinstance(value, list): raise ValueError("array type is not a list value.") elements = [encode_value(vtype[:-2], val, go) for val in value] return sha3(b"".join(elements)) # Should be a struct finally if not isinstance(value, dict): raise ValueError("struct type is not a dict value.") return hash_struct(vtype, go, value)
def type_hash(name, types_obj): """Compute typeHash (hash of the encodeType type string).""" subtypes_list = collect_sub_types(name, types_obj) subtypes_list.sort() types_list = [name, *subtypes_list] return sha3(encode_types(types_list, types_obj).encode("utf8"))
def hash_struct(name, types_obj, data_obj): """Compute the hashStruct = keccak256(typeHash ‖ encodeData(s)).""" return sha3( type_hash(name, types_obj) + encode_data(name, types_obj, data_obj))