From 25a5953b2e091b292d939d11b5eb61970558a0aa Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Fri, 9 Feb 2018 02:04:40 -0800 Subject: [PATCH 1/6] Began work on issue #22 - isValidSignatureForSolidity --- servicesExternal/web3_single.py | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index 63e18e6..f7bd93b 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -182,6 +182,56 @@ def areSameAddressesNoChecksum(self, address1: str, address2: str) -> bool: return False return address1.toLowerCase() == address2.toLowerCase() + def fromRpcSig(self, signature: str) -> Any: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#fromrpcsig + ''' + Convert signature format of the eth_sign RPC method to signature parameters + ''' + # TODO: Implement + return None + + def ecrecover(self, msgHash: Any, blockNumber: int, rBuffer: Any, sBuffer: Any) -> Any: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#ecrecover + ''' + ECDSA public key recovery from signature + ''' + # TODO: Implement + return None + + def hashPersonalMessage(self, message: Any) -> Any: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#hashpersonalmessage + ''' + Returns the keccak-256 hash of message, prefixed with the header used by the eth_sign RPC call. The output of this function + can be fed into ecsign to produce the same signature as the eth_sign call for a given message, or fed to ecrecover along with a + signature to recover the public key used to produce the signature. + ''' + # TODO: Implement + return None + + def toBuffer(self, buffer: Any) -> Any: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#tobuffer + ''' + Attempts to turn a value into a Buffer. As input it supports Buffer, String, Number, null/undefined, BN and other objects with a toArray() method. + ''' + # TODO: Implement + return None + + def pubToAddress(self, buffer: Any) -> Any: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#pubtoaddress + ''' + Returns the ethereum address of a given public key. Accepts Ethereum public keys and SEC1 encoded keys. + ''' + # TODO: Implement + return None + + def bufferToHex(self, buffer: Any) -> str: + # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#buffertohex + ''' + Converts a Buffer into a hex String + ''' + # TODO: Implement + return None + def isHexStrictBytes32(self, hex: str) -> bool: ''' Check if a string is a hex byte @@ -310,8 +360,30 @@ async def getBlockTimestamp(self, blockNumber: int): except Exception as e: raise e + + + def resultToArray(self, obj: Any) -> List[Any]: result: List[Any] = List[] for i in range(len(obj)): result.append(obj[i]) return result + + def isValidSignatureForSolidity(signature: string, hash: string, payee: string) -> bool: + ''' + :param signature: Ethereum signature for use in `eth_sign` RPC method + :param hash: transaction hash + :param payee: payee adress + Check if the provided signature is valid for Solidity + ''' + signatureRPClike = fromRpcSig(signature); + pub = ecrecover(hashPersonalMessage(toBuffer(hash)), signatureRPClike.v, signatureRPClike.r, signatureRPClike.s); + addrBuf = pubToAddress(pub); + addressSigning = bufferToHex(addrBuf); + return areSameAddressesNoChecksum(addressSigning, payee); +} + + + + + From de65d75eb802f7837420d9a54dde717ea5c3d10f Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Fri, 9 Feb 2018 02:10:02 -0800 Subject: [PATCH 2/6] typos and string -> str --- servicesExternal/web3_single.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index f7bd93b..b187107 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -369,11 +369,11 @@ def resultToArray(self, obj: Any) -> List[Any]: result.append(obj[i]) return result - def isValidSignatureForSolidity(signature: string, hash: string, payee: string) -> bool: + def isValidSignatureForSolidity(signature: str, hash: str, payee: str) -> bool: ''' :param signature: Ethereum signature for use in `eth_sign` RPC method :param hash: transaction hash - :param payee: payee adress + :param payee: payee address Check if the provided signature is valid for Solidity ''' signatureRPClike = fromRpcSig(signature); From 9eab4abdc1adf6503d0ac54ef64a045ff809bbea Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Fri, 9 Feb 2018 09:56:30 -0800 Subject: [PATCH 3/6] re-implemented isValidSignatureForSolidity using signature verification code from https://github.com/Neufund/signature-authentication-server --- requirements.txt | 4 +- servicesExternal/web3_single.py | 103 ++++++++++++++------------------ 2 files changed, 48 insertions(+), 59 deletions(-) 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 b187107..97a9478 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,55 +183,49 @@ def areSameAddressesNoChecksum(self, address1: str, address2: str) -> bool: return False return address1.toLowerCase() == address2.toLowerCase() - def fromRpcSig(self, signature: str) -> Any: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#fromrpcsig - ''' - Convert signature format of the eth_sign RPC method to signature parameters - ''' - # TODO: Implement - return None - - def ecrecover(self, msgHash: Any, blockNumber: int, rBuffer: Any, sBuffer: Any) -> Any: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#ecrecover - ''' - ECDSA public key recovery from signature - ''' - # TODO: Implement - return None + def keccak(bytes): + return sha3.keccak_256(bytes).digest() - def hashPersonalMessage(self, message: Any) -> Any: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#hashpersonalmessage + def message_hash(message): ''' - Returns the keccak-256 hash of message, prefixed with the header used by the eth_sign RPC call. The output of this function - can be fed into ecsign to produce the same signature as the eth_sign call for a given message, or fed to ecrecover along with a - signature to recover the public key used to produce the signature. + 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 ''' - # TODO: Implement - return None - - def toBuffer(self, buffer: Any) -> Any: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#tobuffer + if type(message) != bytes: + message = message.encode('utf8') + prefix = '\u0019Ethereum Signed Message:\n' + str(len(message)) + prefix = prefix.encode('utf8') + return keccak(prefix + message) + + def legendre_check(r): + # 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 ecrecover_to_pub(hash, signature, chaincode = 27): ''' - Attempts to turn a value into a Buffer. As input it supports Buffer, String, Number, null/undefined, BN and other objects with a toArray() method. + ECDSA public key recovery from signature + ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#ecrecover ''' - # TODO: Implement - return None - - def pubToAddress(self, buffer: Any) -> Any: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#pubtoaddress + assert len(hash) == 32 + assert len(signature) == 65 + assert legendre_check(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 pub_to_address(pubkey): ''' - Returns the ethereum address of a given public key. Accepts Ethereum public keys and SEC1 encoded keys. + Converts a public key into a hex String + ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#buffertohex ''' - # TODO: Implement - return None + return '0x' + (keccak(pubkey[0:])[12:]).hex() - def bufferToHex(self, buffer: Any) -> str: - # ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#buffertohex - ''' - Converts a Buffer into a hex String - ''' - # TODO: Implement - return None def isHexStrictBytes32(self, hex: str) -> bool: ''' @@ -360,9 +355,6 @@ async def getBlockTimestamp(self, blockNumber: int): except Exception as e: raise e - - - def resultToArray(self, obj: Any) -> List[Any]: result: List[Any] = List[] for i in range(len(obj)): @@ -371,19 +363,14 @@ def resultToArray(self, obj: Any) -> List[Any]: def isValidSignatureForSolidity(signature: str, hash: str, payee: str) -> bool: ''' - :param signature: Ethereum signature for use in `eth_sign` RPC method - :param hash: transaction hash - :param payee: payee address Check if the provided signature is valid for Solidity + :param signature: Ethereum signature + :param hash: transaction message hash + :param payee: payee address ''' - signatureRPClike = fromRpcSig(signature); - pub = ecrecover(hashPersonalMessage(toBuffer(hash)), signatureRPClike.v, signatureRPClike.r, signatureRPClike.s); - addrBuf = pubToAddress(pub); - addressSigning = bufferToHex(addrBuf); - return areSameAddressesNoChecksum(addressSigning, payee); -} - - - - + try: + pubkey = ecrecover_to_pub(hash, signature) # if transaction message is not already hashed then use hash = message_hash(message) + return payee == pub_to_address(pubkey) + except: + return False From da057b6cbabf142544caff3fa2071f2a4e914d44 Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Fri, 9 Feb 2018 10:13:19 -0800 Subject: [PATCH 4/6] add type hints --- servicesExternal/web3_single.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index 97a9478..aee6a2e 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -183,10 +183,10 @@ def areSameAddressesNoChecksum(self, address1: str, address2: str) -> bool: return False return address1.toLowerCase() == address2.toLowerCase() - def keccak(bytes): + def keccak(bytes) -> str: return sha3.keccak_256(bytes).digest() - def message_hash(message): + def message_hash(message) -> 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 @@ -197,13 +197,13 @@ def message_hash(message): prefix = prefix.encode('utf8') return keccak(prefix + message) - def legendre_check(r): + def legendre_check(r) -> 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 ecrecover_to_pub(hash, signature, chaincode = 27): + def ecrecover_to_pub(hash, signature, chaincode = 27) -> str: ''' ECDSA public key recovery from signature ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#ecrecover @@ -219,7 +219,7 @@ def ecrecover_to_pub(hash, signature, chaincode = 27): assert len(pub) == 64 return pub - def pub_to_address(pubkey): + def pub_to_address(pubkey) -> str: ''' Converts a public key into a hex String ethereumjs-util equivalent: https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/index.md#buffertohex @@ -361,7 +361,7 @@ def resultToArray(self, obj: Any) -> List[Any]: result.append(obj[i]) return result - def isValidSignatureForSolidity(signature: str, hash: str, payee: str) -> bool: + def isValidSignatureForSolidity(self, signature: str, hash: str, payee: str) -> bool: ''' Check if the provided signature is valid for Solidity :param signature: Ethereum signature From c92a3e8fcc9a50e24ebde231817a82bc790c7cc5 Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Tue, 27 Feb 2018 19:27:23 -0800 Subject: [PATCH 5/6] use isinstance rather than type; change method naming convention from snake_case to camelCase; added types to all function parameters --- servicesExternal/web3_single.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index aee6a2e..ccab762 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -183,34 +183,34 @@ def areSameAddressesNoChecksum(self, address1: str, address2: str) -> bool: return False return address1.toLowerCase() == address2.toLowerCase() - def keccak(bytes) -> str: - return sha3.keccak_256(bytes).digest() + def keccak(byteMessage: bytes) -> str: + return sha3.keccak_256(byteMessage).digest() - def message_hash(message) -> str: + 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 type(message) != bytes: + if !isinstance(message, bytes): message = message.encode('utf8') prefix = '\u0019Ethereum Signed Message:\n' + str(len(message)) prefix = prefix.encode('utf8') return keccak(prefix + message) - def legendre_check(r) -> bool: + 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 ecrecover_to_pub(hash, signature, chaincode = 27) -> str: + 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 assert len(signature) == 65 - assert legendre_check(int.from_bytes(signature[0:32], 'big')) + 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) @@ -219,7 +219,7 @@ def ecrecover_to_pub(hash, signature, chaincode = 27) -> str: assert len(pub) == 64 return pub - def pub_to_address(pubkey) -> str: + 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 @@ -369,8 +369,8 @@ def isValidSignatureForSolidity(self, signature: str, hash: str, payee: str) -> :param payee: payee address ''' try: - pubkey = ecrecover_to_pub(hash, signature) # if transaction message is not already hashed then use hash = message_hash(message) - return payee == pub_to_address(pubkey) + pubkey = ecrecoverToPub(hash, signature) # if transaction message is not already hashed then use hash = messageHash(message) + return payee == pubToAddress(pubkey) except: return False From 86229222773d045036f0047e5cb2064dc850ffd8 Mon Sep 17 00:00:00 2001 From: Lennart Rudolph Date: Wed, 28 Feb 2018 12:10:05 -0800 Subject: [PATCH 6/6] added explanation for keccak-256 hash length assertion; made a minor syntax fix --- servicesExternal/web3_single.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/servicesExternal/web3_single.py b/servicesExternal/web3_single.py index ccab762..069dd96 100644 --- a/servicesExternal/web3_single.py +++ b/servicesExternal/web3_single.py @@ -191,7 +191,7 @@ 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 !isinstance(message, bytes): + if not isinstance(message, bytes): message = message.encode('utf8') prefix = '\u0019Ethereum Signed Message:\n' + str(len(message)) prefix = prefix.encode('utf8') @@ -208,7 +208,7 @@ 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 + 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