diff --git a/requirements.txt b/requirements.txt index b017276..20516a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ web3 -ipfsapi \ No newline at end of file +ipfsapi +pysha3 +secp256k1 diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index 63e18e6..069dd96 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -5,7 +5,8 @@ from config import config from lib.etherum_abi_perso import toSolidityBytes32 as etherumToSolid - +import secp256k1 +import sha3 class Singleton(type): _instances = {} @@ -182,6 +183,50 @@ def areSameAddressesNoChecksum(self, address1: str, address2: str) -> bool: return False return address1.toLowerCase() == address2.toLowerCase() + def keccak(byteMessage: bytes) -> str: + return sha3.keccak_256(byteMessage).digest() + + def messageHash(message: bytes) -> str: + ''' + Returns the keccak-256 hash of message, prefixed with the header used by the eth_sign RPC call + ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#hashpersonalmessage + ''' + if not isinstance(message, bytes): + message = message.encode('utf8') + prefix = '\u0019Ethereum Signed Message:\n' + str(len(message)) + prefix = prefix.encode('utf8') + return keccak(prefix + message) + + def legendreCheck(r: int) -> bool: + # Legendre symbol check; the secp256k1 library does not seem to do this + SECP256K1P = 2**256 - 4294968273 + xc = r * r * r + 7 + return pow(xc, (SECP256K1P - 1) // 2, SECP256K1P) == 1 + + def ecrecoverToPub(hash: str, signature: str, chaincode: int = 27) -> str: + ''' + ECDSA public key recovery from signature + ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#ecrecover + ''' + assert len(hash) == 32 # Keccak-256 in Solidity returns a bytestring of length 32, so our hash must meet this condition + assert len(signature) == 65 + assert legendreCheck(int.from_bytes(signature[0:32], 'big')) + hint = signature[-1] - chaincode + pk = secp256k1.PublicKey(flags=secp256k1.ALL_FLAGS) + recover_sig = pk.ecdsa_recoverable_deserialize(signature[:-1], hint) + pk.public_key = pk.ecdsa_recover(hash, recover_sig, raw=True) + pub = pk.serialize(compressed=False)[1:] + assert len(pub) == 64 + return pub + + def pubToAddress(pubkey: str) -> str: + ''' + Converts a public key into a hex String + ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#buffertohex + ''' + return '0x' + (keccak(pubkey[0:])[12:]).hex() + + def isHexStrictBytes32(self, hex: str) -> bool: ''' Check if a string is a hex byte @@ -315,3 +360,17 @@ def resultToArray(self, obj: Any) -> List[Any]: for i in range(len(obj)): result.append(obj[i]) return result + + def isValidSignatureForSolidity(self, signature: str, hash: str, payee: str) -> bool: + ''' + Check if the provided signature is valid for Solidity + :param signature: Ethereum signature + :param hash: transaction message hash + :param payee: payee address + ''' + try: + pubkey = ecrecoverToPub(hash, signature) # if transaction message is not already hashed then use hash = messageHash(message) + return payee == pubToAddress(pubkey) + except: + return False +