From 35a2e0f45ddda8289f77b05c6a6bdd4a1bfa616b Mon Sep 17 00:00:00 2001 From: sea212 Date: Sat, 25 Aug 2018 18:53:33 +0200 Subject: [PATCH 01/27] Create hdaccount.py, specified class HDAccount, added instructions what to do next --- eth_account/hdaccount.py | 137 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 eth_account/hdaccount.py diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py new file mode 100644 index 00000000..62b6cc1b --- /dev/null +++ b/eth_account/hdaccount.py @@ -0,0 +1,137 @@ +from bitcoin import ( + deterministic, + mnemonic, +) + +from hexbytes import ( + HexBytes, +) + +from eth_account.datastructures import ( + AttributeDict, +) + +from eth_account.signers.base import ( + BaseAccount, +) + + +class HDAccount(BaseAccount): + ''' + This class manages BIP32 HD-Accounts for Ethereum + ''' + + def __init__(self, privkey : str = "", pubkey : tuple = ("",""), chaindata : str = ""): + ''' The object is getting initialized here. You can pass no parameters, + in which case the object will be created containing no information. + You can create an account later by calling initAccount(...) in this case. + You can pass only a private key and chaindata, in which case the + public key will be automatically calculated. You can derive keypairs. + You can pass only a public key and chaindata, in which case only + public keys can be derived. + Pass the arguments as hex strings. + ''' + + # bip32 magic number for derivation of hardened childs + self.__hardened = 0x80000000 + + if (privkey,pubkey,chaindata) == ("",("",""),""): + # create uninitialized account + self._privkey = self._chaindata = b"" + self._pubkey = (b"", b"") + + elif (privkey,chaindata) != ("",""): + # initialize account with a private key and a chaincode + # TODO + # TODO derive public key + + elif (pubkey,chaindata) != ("",""): + # initialize account only with a public key and a chaincode + # TODO + + + # Initiate account generator + self._accgen = self.__accountGenerator(0) + next(self._accgen) + + def __accountGenerator(self, curindex : int = 0): + ''' This is the account generator used to derive all desired + children keys. It is ought to be used only internally. + You can either send None to this generator, in which case it + just increments the index and returns you the derived child object + for that index or you can send an index. + If no private key is specified but a public key is specified, + the derived child will be derived using only the public key + ''' + + # This variable will contain the object of the last derived child + newacc = None; + + while True: + cid = yield newacc + + if (cid != None): + if (type(cid) != type(1)): + raise TypeError("Invalid child index type. Excepted int") + + curindex = cid + + # TODO derive child into a new HDAccount object + # z = HMAC-SHA512(chaincode, (maybe hardneed prefix) || compr. pubkey/privkey || index) + # derivation index = z[:16] + # new chaincode: z[16:] + # private key: privkey += index + # public key: publickey = pubkey ECC_ADD index ECC_MUL generator_point + curindex += 1 + + def deriveChild(self, cid : int = None) -> HDAccount: + ''' This function generates a new account by using the + __accountGenerator function. + ''' + # TODO implement this gateway function for __accountGenerator(...) + + def createAccount(self, password : str = "", ent_bits : int = 256) -> str: + ''' This function initiates an account from scratch + After completing the initiation it returns the mnemonic code + ''' + # TODO random from ent pool -> add checksum -> create mnemonic -> + # PBKDF2-HMAC-SHA512(mnemonic, pw) -> HMAC-SHA512(u"Bitcoin Seed", rootseed) + # -> 32 Byte priv key | 32 Byte chaindata + pass + + @property + def address(self) -> str: + ''' + The checksummed public address for this account. + .. code-block:: python + >>> my_account.address + "0xF0109fC8DF283027b6285cc889F5aA624EaC1F55" + ''' + pass + + def signHash(self, message_hash : bytes) -> AttributeDict: + ''' + Sign the hash of a message, as in :meth:`~eth_account.account.Account.signHash` + but without specifying the private key. + :var bytes message_hash: 32 byte hash of the message to sign + ''' + pass + + def signTransaction(self, transaction_dict : Mapping) -> AttributeDict: + ''' + Sign a transaction, as in :meth:`~eth_account.account.Account.signTransaction` + but without specifying the private key. + :var dict transaction_dict: transaction with all fields specified + ''' + pass + + def __eq__(self, other): + ''' + Equality test between two accounts. + Two accounts are considered the same if they are exactly the same type, + and can sign for the same address. + ''' + return type(self) == type(other) and self.address == other.address + + def __hash__(self): + return hash((type(self), self.address)) From 97f136b46b2dcd99c95f9182199dface22b1b274 Mon Sep 17 00:00:00 2001 From: sea212 Date: Sat, 25 Aug 2018 19:10:44 +0200 Subject: [PATCH 02/27] Minor additions --- eth_account/hdaccount.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 62b6cc1b..fd489472 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -51,10 +51,10 @@ def __init__(self, privkey : str = "", pubkey : tuple = ("",""), chaindata : str # Initiate account generator - self._accgen = self.__accountGenerator(0) + self._accgen = self._accountGenerator(0) next(self._accgen) - def __accountGenerator(self, curindex : int = 0): + def _accountGenerator(self, curindex : int = 0): ''' This is the account generator used to derive all desired children keys. It is ought to be used only internally. You can either send None to this generator, in which case it @@ -86,9 +86,12 @@ def __accountGenerator(self, curindex : int = 0): def deriveChild(self, cid : int = None) -> HDAccount: ''' This function generates a new account by using the - __accountGenerator function. + __accountGenerator function. You can specify an index. + Not specifying an index leads to the usage of the old index + 1 ''' # TODO implement this gateway function for __accountGenerator(...) + # check if this object is initialized + return self.__accgen.send(cid) def createAccount(self, password : str = "", ent_bits : int = 256) -> str: ''' This function initiates an account from scratch @@ -99,7 +102,7 @@ def createAccount(self, password : str = "", ent_bits : int = 256) -> str: # -> 32 Byte priv key | 32 Byte chaindata pass - @property + @property def address(self) -> str: ''' The checksummed public address for this account. From 8f6676e1f67264a73e36780c511fb901e7c0a47b Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 27 Aug 2018 00:51:16 +0200 Subject: [PATCH 03/27] Minor changes so the linter is happy --- eth_account/hdaccount.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index fd489472..3df0ce15 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -21,11 +21,11 @@ class HDAccount(BaseAccount): This class manages BIP32 HD-Accounts for Ethereum ''' - def __init__(self, privkey : str = "", pubkey : tuple = ("",""), chaindata : str = ""): + def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), chaindata: str = ""): ''' The object is getting initialized here. You can pass no parameters, in which case the object will be created containing no information. You can create an account later by calling initAccount(...) in this case. - You can pass only a private key and chaindata, in which case the + You can pass only a private key and chaindata, in which case the public key will be automatically calculated. You can derive keypairs. You can pass only a public key and chaindata, in which case only public keys can be derived. @@ -35,26 +35,25 @@ def __init__(self, privkey : str = "", pubkey : tuple = ("",""), chaindata : str # bip32 magic number for derivation of hardened childs self.__hardened = 0x80000000 - if (privkey,pubkey,chaindata) == ("",("",""),""): + if (privkey,pubkey,chaindata) == ("", ("", ""), ""): # create uninitialized account self._privkey = self._chaindata = b"" self._pubkey = (b"", b"") - elif (privkey,chaindata) != ("",""): + elif (privkey,chaindata) != ("", ""): # initialize account with a private key and a chaincode # TODO # TODO derive public key - - elif (pubkey,chaindata) != ("",""): + + elif (pubkey,chaindata) != ("", ""): # initialize account only with a public key and a chaincode # TODO - - + # Initiate account generator self._accgen = self._accountGenerator(0) next(self._accgen) - def _accountGenerator(self, curindex : int = 0): + def _accountGenerator(self, curindex: int = 0): ''' This is the account generator used to derive all desired children keys. It is ought to be used only internally. You can either send None to this generator, in which case it @@ -65,17 +64,17 @@ def _accountGenerator(self, curindex : int = 0): ''' # This variable will contain the object of the last derived child - newacc = None; + newacc = None while True: cid = yield newacc - if (cid != None): - if (type(cid) != type(1)): + if cid is not None: + if not isinstance(cid, int): raise TypeError("Invalid child index type. Excepted int") curindex = cid - + # TODO derive child into a new HDAccount object # z = HMAC-SHA512(chaincode, (maybe hardneed prefix) || compr. pubkey/privkey || index) # derivation index = z[:16] @@ -84,7 +83,7 @@ def _accountGenerator(self, curindex : int = 0): # public key: publickey = pubkey ECC_ADD index ECC_MUL generator_point curindex += 1 - def deriveChild(self, cid : int = None) -> HDAccount: + def deriveChild(self, cid: int = None) -> HDAccount: ''' This function generates a new account by using the __accountGenerator function. You can specify an index. Not specifying an index leads to the usage of the old index + 1 @@ -93,7 +92,7 @@ def deriveChild(self, cid : int = None) -> HDAccount: # check if this object is initialized return self.__accgen.send(cid) - def createAccount(self, password : str = "", ent_bits : int = 256) -> str: + def createAccount(self, password: str = "", ent_bits: int = 256) -> str: ''' This function initiates an account from scratch After completing the initiation it returns the mnemonic code ''' @@ -112,7 +111,7 @@ def address(self) -> str: ''' pass - def signHash(self, message_hash : bytes) -> AttributeDict: + def signHash(self, message_hash: bytes) -> AttributeDict: ''' Sign the hash of a message, as in :meth:`~eth_account.account.Account.signHash` but without specifying the private key. @@ -120,7 +119,7 @@ def signHash(self, message_hash : bytes) -> AttributeDict: ''' pass - def signTransaction(self, transaction_dict : Mapping) -> AttributeDict: + def signTransaction(self, transaction_dict: Mapping) -> AttributeDict: ''' Sign a transaction, as in :meth:`~eth_account.account.Account.signTransaction` but without specifying the private key. From 5b4c2ce830f45e86d67e719f81765ffc2fec9535 Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 27 Aug 2018 00:58:15 +0200 Subject: [PATCH 04/27] linter --- eth_account/hdaccount.py | 58 ++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 3df0ce15..6693c1a0 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -22,45 +22,47 @@ class HDAccount(BaseAccount): ''' def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), chaindata: str = ""): - ''' The object is getting initialized here. You can pass no parameters, - in which case the object will be created containing no information. - You can create an account later by calling initAccount(...) in this case. - You can pass only a private key and chaindata, in which case the - public key will be automatically calculated. You can derive keypairs. - You can pass only a public key and chaindata, in which case only - public keys can be derived. - Pass the arguments as hex strings. + ''' + The object is getting initialized here. You can pass no parameters, + in which case the object will be created containing no information. + You can create an account later by calling initAccount(...) in this case. + You can pass only a private key and chaindata, in which case the + public key will be automatically calculated. You can derive keypairs. + You can pass only a public key and chaindata, in which case only + public keys can be derived. + Pass the arguments as hex strings. ''' # bip32 magic number for derivation of hardened childs self.__hardened = 0x80000000 - if (privkey,pubkey,chaindata) == ("", ("", ""), ""): + if (privkey, pubkey, chaindata) == ("", ("", ""), ""): # create uninitialized account self._privkey = self._chaindata = b"" self._pubkey = (b"", b"") - elif (privkey,chaindata) != ("", ""): + elif (privkey, chaindata) != ("", ""): # initialize account with a private key and a chaincode # TODO # TODO derive public key - - elif (pubkey,chaindata) != ("", ""): + + elif (pubkey, chaindata) != ("", ""): # initialize account only with a public key and a chaincode # TODO - + # Initiate account generator self._accgen = self._accountGenerator(0) next(self._accgen) def _accountGenerator(self, curindex: int = 0): - ''' This is the account generator used to derive all desired - children keys. It is ought to be used only internally. - You can either send None to this generator, in which case it - just increments the index and returns you the derived child object - for that index or you can send an index. - If no private key is specified but a public key is specified, - the derived child will be derived using only the public key + ''' + This is the account generator used to derive all desired + children keys. It is ought to be used only internally. + You can either send None to this generator, in which case it + just increments the index and returns you the derived child object + for that index or you can send an index. + If no private key is specified but a public key is specified, + the derived child will be derived using only the public key ''' # This variable will contain the object of the last derived child @@ -84,22 +86,26 @@ def _accountGenerator(self, curindex: int = 0): curindex += 1 def deriveChild(self, cid: int = None) -> HDAccount: - ''' This function generates a new account by using the - __accountGenerator function. You can specify an index. - Not specifying an index leads to the usage of the old index + 1 + ''' + This function generates a new account by using the + __accountGenerator function. You can specify an index. + Not specifying an index leads to the usage of the old index + 1 ''' + # TODO implement this gateway function for __accountGenerator(...) # check if this object is initialized return self.__accgen.send(cid) def createAccount(self, password: str = "", ent_bits: int = 256) -> str: - ''' This function initiates an account from scratch - After completing the initiation it returns the mnemonic code ''' + This function initiates an account from scratch + After completing the initiation it returns the mnemonic code + ''' + # TODO random from ent pool -> add checksum -> create mnemonic -> # PBKDF2-HMAC-SHA512(mnemonic, pw) -> HMAC-SHA512(u"Bitcoin Seed", rootseed) # -> 32 Byte priv key | 32 Byte chaindata - pass + pass @property def address(self) -> str: From 006c1461d849734280f80cd4130fa68f06505b9d Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 27 Aug 2018 01:37:59 +0200 Subject: [PATCH 05/27] linter: installed flake8 now --- eth_account/hdaccount.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 6693c1a0..511626e7 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -22,7 +22,7 @@ class HDAccount(BaseAccount): ''' def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), chaindata: str = ""): - ''' + ''' The object is getting initialized here. You can pass no parameters, in which case the object will be created containing no information. You can create an account later by calling initAccount(...) in this case. @@ -45,17 +45,19 @@ def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), chaindata: str = # initialize account with a private key and a chaincode # TODO # TODO derive public key + pass elif (pubkey, chaindata) != ("", ""): # initialize account only with a public key and a chaincode # TODO + pass # Initiate account generator self._accgen = self._accountGenerator(0) next(self._accgen) def _accountGenerator(self, curindex: int = 0): - ''' + ''' This is the account generator used to derive all desired children keys. It is ought to be used only internally. You can either send None to this generator, in which case it @@ -76,7 +78,7 @@ def _accountGenerator(self, curindex: int = 0): raise TypeError("Invalid child index type. Excepted int") curindex = cid - + # TODO derive child into a new HDAccount object # z = HMAC-SHA512(chaincode, (maybe hardneed prefix) || compr. pubkey/privkey || index) # derivation index = z[:16] @@ -86,7 +88,7 @@ def _accountGenerator(self, curindex: int = 0): curindex += 1 def deriveChild(self, cid: int = None) -> HDAccount: - ''' + ''' This function generates a new account by using the __accountGenerator function. You can specify an index. Not specifying an index leads to the usage of the old index + 1 From e9fb77dfe76170ac9b3b9a2066e0b75d75542a52 Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 27 Aug 2018 02:06:19 +0200 Subject: [PATCH 06/27] linter --- eth_account/hdaccount.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 511626e7..29fd5936 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -1,3 +1,4 @@ +''' from bitcoin import ( deterministic, mnemonic, @@ -6,6 +7,7 @@ from hexbytes import ( HexBytes, ) +''' from eth_account.datastructures import ( AttributeDict, @@ -21,7 +23,8 @@ class HDAccount(BaseAccount): This class manages BIP32 HD-Accounts for Ethereum ''' - def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), chaindata: str = ""): + def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), + chaindata: str = ""): ''' The object is getting initialized here. You can pass no parameters, in which case the object will be created containing no information. @@ -87,7 +90,7 @@ def _accountGenerator(self, curindex: int = 0): # public key: publickey = pubkey ECC_ADD index ECC_MUL generator_point curindex += 1 - def deriveChild(self, cid: int = None) -> HDAccount: + def deriveChild(self, cid: int = None) -> "HDAccount": ''' This function generates a new account by using the __accountGenerator function. You can specify an index. @@ -127,7 +130,7 @@ def signHash(self, message_hash: bytes) -> AttributeDict: ''' pass - def signTransaction(self, transaction_dict: Mapping) -> AttributeDict: + def signTransaction(self, transaction_dict: dict) -> AttributeDict: ''' Sign a transaction, as in :meth:`~eth_account.account.Account.signTransaction` but without specifying the private key. From 7ee5bb445b1914f8aff414650acaf76400efacde Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 27 Aug 2018 02:15:03 +0200 Subject: [PATCH 07/27] sorted imports --- eth_account/hdaccount.py | 1 - 1 file changed, 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 29fd5936..f0411a0c 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -12,7 +12,6 @@ from eth_account.datastructures import ( AttributeDict, ) - from eth_account.signers.base import ( BaseAccount, ) From 05c096da050fed59ce540a06cf9676c0a1b070e7 Mon Sep 17 00:00:00 2001 From: sea212 Date: Fri, 31 Aug 2018 15:59:48 +0200 Subject: [PATCH 08/27] Added mnemonic.py from pybitcointools. Applied linter and style changes --- eth_account/mnemonic.py | 166 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 eth_account/mnemonic.py diff --git a/eth_account/mnemonic.py b/eth_account/mnemonic.py new file mode 100644 index 00000000..dded87da --- /dev/null +++ b/eth_account/mnemonic.py @@ -0,0 +1,166 @@ +import hashlib +import os.path +import binascii +import random +from bisect import bisect_left + +''' +This library was included from pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/mnemonic.py +linter and style modifications have been applied +''' + +wordlist_english = open(os.path.join(os.path.dirname( + os.path.realpath(__file__)), 'english.txt'), 'r') + + +def eint_to_bytes(entint, entbits): + a = hex(entint)[2:].rstrip('L').zfill(32) + print(a) + return binascii.unhexlify(a) + + +def mnemonic_int_to_words(mint, mint_num_words, wordlist=wordlist_english): + backwords = [wordlist[(mint >> (11 * x)) & 0x7FF].strip() for x in range(mint_num_words)] + return backwords[::-1] + + +def entropy_cs(entbytes): + entropy_size = 8 * len(entbytes) + checksum_size = entropy_size // 32 + hd = hashlib.sha256(entbytes).hexdigest() + csint = int(hd, 16) >> (256 - checksum_size) + return csint, checksum_size + + +def entropy_to_words(entbytes, wordlist=wordlist_english): + if(len(entbytes) < 4 or len(entbytes) % 4 != 0): + raise ValueError("The size of the entropy must be a multiple of" + "4 bytes (multiple of 32 bits)") + + entropy_size = 8 * len(entbytes) + csint, checksum_size = entropy_cs(entbytes) + entint = int(binascii.hexlify(entbytes), 16) + mint = (entint << checksum_size) | csint + mint_num_words = (entropy_size + checksum_size) // 11 + + return mnemonic_int_to_words(mint, mint_num_words, wordlist) + + +def words_bisect(word, wordlist=wordlist_english): + lo = bisect_left(wordlist, word) + hi = len(wordlist) - bisect_left(wordlist[:lo:-1], word) + + return lo, hi + + +def words_split(wordstr, wordlist=wordlist_english): + def popword(wordstr, wordlist): + for fwl in range(1, 9): + w = wordstr[:fwl].strip() + lo, hi = words_bisect(w, wordlist) + + if(hi - lo == 1): + return w, wordstr[fwl:].lstrip() + + wordlist = wordlist[lo:hi] + + raise Exception("Wordstr %s not found in list" % w) + + words = [] + tail = wordstr + + while(len(tail)): + head, tail = popword(tail, wordlist) + words.append(head) + + return words + + +def words_to_mnemonic_int(words, wordlist=wordlist_english): + if(isinstance(words, str)): + words = words_split(words, wordlist) + return sum([wordlist.index(w) << (11 * x) for x, w in enumerate(words[::-1])]) + + +def words_verify(words, wordlist=wordlist_english): + if(isinstance(words, str)): + words = words_split(words, wordlist) + + mint = words_to_mnemonic_int(words, wordlist) + mint_bits = len(words) * 11 + cs_bits = mint_bits // 32 + entropy_bits = mint_bits - cs_bits + eint = mint >> cs_bits + csint = mint & ((1 << cs_bits) - 1) + ebytes = eint_to_bytes(eint, entropy_bits) + return csint == entropy_cs(ebytes) + + +def mnemonic_to_seed(mnemonic_phrase, passphrase=b''): + try: + from hashlib import pbkdf2_hmac + + def pbkdf2_hmac_sha256(password, salt, iters=2048): + return pbkdf2_hmac(hash_name='sha512', password=password, + salt=salt, iterations=iters) + except ImportError: + try: + from Crypto.Protocol.KDF import PBKDF2 + from Crypto.Hash import SHA512, HMAC + + def pbkdf2_hmac_sha256(password, salt, iters=2048): + return PBKDF2(password=password, salt=salt, dkLen=64, + count=iters, prf=lambda p, s: + HMAC.new(p, s, SHA512).digest()) + except ImportError: + try: + + from pbkdf2 import PBKDF2 + import hmac + + def pbkdf2_hmac_sha256(password, salt, iters=2048): + return PBKDF2(password, salt, iterations=iters, + macmodule=hmac, + digestmodule=hashlib.sha512).read(64) + + except ImportError: + raise RuntimeError("No implementation of pbkdf2 was found!") + + return pbkdf2_hmac_sha256(password=mnemonic_phrase, + salt=b'mnemonic' + passphrase) + + +def words_mine(prefix, entbits, satisfunction, wordlist=wordlist_english, + randombits=random.getrandbits): + prefix_bits = len(prefix) * 11 + mine_bits = entbits - prefix_bits + pint = words_to_mnemonic_int(prefix, wordlist) + pint <<= mine_bits + dint = randombits(mine_bits) + count = 0 + + while(not satisfunction(entropy_to_words(eint_to_bytes(pint + dint, entbits)))): + dint = randombits(mine_bits) + + if((count & 0xFFFF) == 0): + print("Searched %f percent of the space" % + (float(count) / float(1 << mine_bits))) + + return entropy_to_words(eint_to_bytes(pint + dint, entbits)) + + +if __name__ == "__main__": + import json + + testvectors = json.load(open('vectors.json', 'r')) + passed = True + + for v in testvectors['english']: + ebytes = binascii.unhexlify(v[0]) + w = ' '.join(entropy_to_words(ebytes)) + seed = mnemonic_to_seed(w, passphrase='TREZOR') + passed = passed and w == v[1] + passed = passed and binascii.hexlify(seed) == v[2] + + print("Tests %s." % ("Passed" if passed else "Failed")) From e45d0d50dbc19589eb75270439fd161706e74fa4 Mon Sep 17 00:00:00 2001 From: sea212 Date: Fri, 31 Aug 2018 16:10:14 +0200 Subject: [PATCH 09/27] Added some describing comments to functions inside mnemonic.py. Changed code to required entropy byte count be an element of [16,20,24,28,32], as described in BIP32 --- eth_account/mnemonic.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/eth_account/mnemonic.py b/eth_account/mnemonic.py index dded87da..8a3f8cb1 100644 --- a/eth_account/mnemonic.py +++ b/eth_account/mnemonic.py @@ -1,8 +1,10 @@ +import binascii +from bisect import ( + bisect_left, +) import hashlib import os.path -import binascii import random -from bisect import bisect_left ''' This library was included from pybitcointools @@ -33,10 +35,11 @@ def entropy_cs(entbytes): return csint, checksum_size +# Call this to create a mnemonic phrase. +# Possible values for entbytes (entropy bytes) are 16, 20, 24, 28 and 32 def entropy_to_words(entbytes, wordlist=wordlist_english): - if(len(entbytes) < 4 or len(entbytes) % 4 != 0): - raise ValueError("The size of the entropy must be a multiple of" - "4 bytes (multiple of 32 bits)") + if entbytes not in [16, 20, 24, 28, 32]: + raise ValueError("entropy must be an element of [16, 20, 24, 28, 32]") entropy_size = 8 * len(entbytes) csint, checksum_size = entropy_cs(entbytes) @@ -83,6 +86,7 @@ def words_to_mnemonic_int(words, wordlist=wordlist_english): return sum([wordlist.index(w) << (11 * x) for x, w in enumerate(words[::-1])]) +# BIP32 checksum verification def words_verify(words, wordlist=wordlist_english): if(isinstance(words, str)): words = words_split(words, wordlist) @@ -97,6 +101,7 @@ def words_verify(words, wordlist=wordlist_english): return csint == entropy_cs(ebytes) +# Derive a root seed which can be used to derive the master private key and chaincode def mnemonic_to_seed(mnemonic_phrase, passphrase=b''): try: from hashlib import pbkdf2_hmac @@ -131,6 +136,7 @@ def pbkdf2_hmac_sha256(password, salt, iters=2048): salt=b'mnemonic' + passphrase) +# Relevant for Electrum style mnemonics def words_mine(prefix, entbits, satisfunction, wordlist=wordlist_english, randombits=random.getrandbits): prefix_bits = len(prefix) * 11 From e5310bc5011eeddce8c0f41b22ef9c4c6f00c5a9 Mon Sep 17 00:00:00 2001 From: sea212 Date: Fri, 31 Aug 2018 18:11:55 +0200 Subject: [PATCH 10/27] added further libraries, linter adjustions (most time), commented unrelevant code --- eth_account/deterministic.py | 210 +++ eth_account/hdaccount/bip39_english.txt | 2048 +++++++++++++++++++++++ eth_account/hdaccount/deterministic.py | 224 +++ eth_account/{ => hdaccount}/mnemonic.py | 5 +- eth_account/hdaccount/pyspecials.py | 231 +++ eth_account/hdaccount/utils.py | 706 ++++++++ eth_account/utils.py | 686 ++++++++ 7 files changed, 4107 insertions(+), 3 deletions(-) create mode 100644 eth_account/deterministic.py create mode 100644 eth_account/hdaccount/bip39_english.txt create mode 100644 eth_account/hdaccount/deterministic.py rename eth_account/{ => hdaccount}/mnemonic.py (96%) create mode 100644 eth_account/hdaccount/pyspecials.py create mode 100644 eth_account/hdaccount/utils.py create mode 100644 eth_account/utils.py diff --git a/eth_account/deterministic.py b/eth_account/deterministic.py new file mode 100644 index 00000000..c10fbab3 --- /dev/null +++ b/eth_account/deterministic.py @@ -0,0 +1,210 @@ +#from bitcoin.main import * +from utils import * +import hmac +import hashlib +#from binascii import hexlify + + +''' +This library was initially included from pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py +''' + +# Electrum wallets + +def electrum_stretch(seed): + return slowsha(seed) + +# Accepts seed or stretched seed, returns master public key + + +def electrum_mpk(seed): + if len(seed) == 32: + seed = electrum_stretch(seed) + return privkey_to_pubkey(seed)[2:] + +# Accepts (seed or stretched seed), index and secondary index +# (conventionally 0 for ordinary addresses, 1 for change) , returns privkey + + +def electrum_privkey(seed, n, for_change=0): + if len(seed) == 32: + seed = electrum_stretch(seed) + mpk = electrum_mpk(seed) + offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+binascii.unhexlify(mpk)) + return add_privkeys(seed, offset) + +# Accepts (seed or stretched seed or master pubkey), index and secondary index +# (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey + + +def electrum_pubkey(masterkey, n, for_change=0): + if len(masterkey) == 32: + mpk = electrum_mpk(electrum_stretch(masterkey)) + elif len(masterkey) == 64: + mpk = electrum_mpk(masterkey) + else: + mpk = masterkey + bin_mpk = encode_pubkey(mpk, 'bin_electrum') + offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+bin_mpk) + return add_pubkeys('04'+mpk, privtopub(offset)) + +# seed/stretched seed/pubkey -> address (convenience method) + + +def electrum_address(masterkey, n, for_change=0, version=0): + return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), version) + +# Given a master public key, a private key from that wallet and its index, +# cracks the secret exponent which can be used to generate all other private +# keys in the wallet + + +def crack_electrum_wallet(mpk, pk, n, for_change=0): + bin_mpk = encode_pubkey(mpk, 'bin_electrum') + offset = dbl_sha256(str(n)+':'+str(for_change)+':'+bin_mpk) + return subtract_privkeys(pk, offset) + +# Below code ASSUMES binary inputs and compressed pubkeys +MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' +MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' +TESTNET_PRIVATE = b'\x04\x35\x83\x94' +TESTNET_PUBLIC = b'\x04\x35\x87\xCF' +PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] +PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] + +# BIP32 child key derivation + + +def raw_bip32_ckd(rawtuple, i): + vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple + i = int(i) + + if vbytes in PRIVATE: + priv = key + pub = privtopub(key) + else: + pub = key + + if i >= 2**31: + if vbytes in PUBLIC: + raise Exception("Can't do private derivation on public key!") + I = hmac.new(chaincode, b'\x00'+priv[:32]+encode(i, 256, 4), hashlib.sha512).digest() + else: + I = hmac.new(chaincode, pub+encode(i, 256, 4), hashlib.sha512).digest() + + if vbytes in PRIVATE: + newkey = add_privkeys(I[:32]+B'\x01', priv) + fingerprint = bin_hash160(privtopub(key))[:4] + if vbytes in PUBLIC: + newkey = add_pubkeys(compress(privtopub(I[:32])), key) + fingerprint = bin_hash160(key)[:4] + + return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) + + +def bip32_serialize(rawtuple): + vbytes, depth, fingerprint, i, chaincode, key = rawtuple + i = encode(i, 256, 4) + chaincode = encode(hash_to_int(chaincode), 256, 32) + keydata = b'\x00'+key[:-1] if vbytes in PRIVATE else key + bindata = vbytes + from_int_to_byte(depth % 256) + fingerprint + i + chaincode + keydata + return changebase(bindata+bin_dbl_sha256(bindata)[:4], 256, 58) + + +def bip32_deserialize(data): + dbin = changebase(data, 58, 256) + if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: + raise Exception("Invalid checksum") + vbytes = dbin[0:4] + depth = from_byte_to_int(dbin[4]) + fingerprint = dbin[5:9] + i = decode(dbin[9:13], 256) + chaincode = dbin[13:45] + key = dbin[46:78]+b'\x01' if vbytes in PRIVATE else dbin[45:78] + return (vbytes, depth, fingerprint, i, chaincode, key) + + +def raw_bip32_privtopub(rawtuple): + vbytes, depth, fingerprint, i, chaincode, key = rawtuple + newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC + return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key)) + + +def bip32_privtopub(data): + return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) + + +def bip32_ckd(data, i): + return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) + + +def bip32_master_key(seed, vbytes=MAINNET_PRIVATE): + I = hmac.new( + from_string_to_bytes("Bitcoin seed"), + from_string_to_bytes(seed), + hashlib.sha512 + ).digest() + return bip32_serialize((vbytes, 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01')) + + +def bip32_bin_extract_key(data): + return bip32_deserialize(data)[-1] + + +def bip32_extract_key(data): + return safe_hexlify(bip32_deserialize(data)[-1]) + +# Exploits the same vulnerability as above in Electrum wallets +# Takes a BIP32 pubkey and one of the child privkeys of its corresponding +# privkey and returns the BIP32 privkey associated with that pubkey + + +def raw_crack_bip32_privkey(parent_pub, priv): + vbytes, depth, fingerprint, i, chaincode, key = priv + pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub + i = int(i) + + if i >= 2**31: + raise Exception("Can't crack private derivation!") + + I = hmac.new(pchaincode, pkey+encode(i, 256, 4), hashlib.sha512).digest() + + pprivkey = subtract_privkeys(key, I[:32]+b'\x01') + + newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE + return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) + + +def crack_bip32_privkey(parent_pub, priv): + dsppub = bip32_deserialize(parent_pub) + dspriv = bip32_deserialize(priv) + return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv)) + + +def coinvault_pub_to_bip32(*args): + if len(args) == 1: + args = args[0].split(' ') + vals = map(int, args[34:]) + I1 = ''.join(map(chr, vals[:33])) + I2 = ''.join(map(chr, vals[35:67])) + return bip32_serialize((MAINNET_PUBLIC, 0, b'\x00'*4, 0, I2, I1)) + + +def coinvault_priv_to_bip32(*args): + if len(args) == 1: + args = args[0].split(' ') + vals = map(int, args[34:]) + I2 = ''.join(map(chr, vals[35:67])) + I3 = ''.join(map(chr, vals[72:104])) + return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01')) + + +def bip32_descend(*args): + if len(args) == 2 and isinstance(args[1], list): + key, path = args + else: + key, path = args[0], map(int, args[1:]) + for p in path: + key = bip32_ckd(key, p) +return bip32_extract_key(key) diff --git a/eth_account/hdaccount/bip39_english.txt b/eth_account/hdaccount/bip39_english.txt new file mode 100644 index 00000000..942040ed --- /dev/null +++ b/eth_account/hdaccount/bip39_english.txt @@ -0,0 +1,2048 @@ +abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py new file mode 100644 index 00000000..b2b5b0c2 --- /dev/null +++ b/eth_account/hdaccount/deterministic.py @@ -0,0 +1,224 @@ +import hashlib +import hmac + +import pyspecials +import utils + +# from binascii import unhexlify +# Electrum wallets + + +''' +This library was included from pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py +''' + +''' + +def electrum_stretch(seed): + return utils.slowsha(seed) + +# Accepts seed or stretched seed, returns master public key + + +def electrum_mpk(seed): + if len(seed) == 32: + seed = electrum_stretch(seed) + return utils.privkey_to_pubkey(seed)[2:] + +# Accepts (seed or stretched seed), index and secondary index +# (conventionally 0 for ordinary addresses, 1 for change) , returns privkey + + +def electrum_privkey(seed, n, for_change=0): + if len(seed) == 32: + seed = electrum_stretch(seed) + + mpk = electrum_mpk(seed) + offset = utils.dbl_sha256(pyspecials.from_int_representation_to_bytes(n) + b':' + + pyspecials.from_int_representation_to_bytes(for_change) + + b':' + unhexlify(mpk)) + return utils.add_privkeys(seed, offset) + +# Accepts (seed or stretched seed or master pubkey), index and secondary index +# (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey + + +def electrum_pubkey(masterkey, n, for_change=0): + if len(masterkey) == 32: + mpk = electrum_mpk(electrum_stretch(masterkey)) + elif len(masterkey) == 64: + mpk = electrum_mpk(masterkey) + else: + mpk = masterkey + + bin_mpk = utils.encode_pubkey(mpk, 'bin_electrum') + offset = pyspecials.bin_dbl_sha256(pyspecials.from_int_representation_to_bytes(n) + b':' + + pyspecials.from_int_representation_to_bytes(for_change) + + b':' + bin_mpk) + return utils.add_pubkeys('04' + mpk, utils.privtopub(offset)) + +# seed/stretched seed/pubkey -> address (convenience method) + + +def electrum_address(masterkey, n, for_change=0, version=0): + return utils.pubkey_to_address(electrum_pubkey(masterkey, n, for_change), version) + + +# Given a master public key, a private key from that wallet and its index, +# cracks the secret exponent which can be used to generate all other private +# keys in the wallet + + +def crack_electrum_wallet(mpk, pk, n, for_change=0): + bin_mpk = utils.encode_pubkey(mpk, 'bin_electrum') + offset = utils.dbl_sha256(str(n) + ':' + str(for_change) + ':' + bin_mpk) + return utils.subtract_privkeys(pk, offset) + +''' + +# Below code ASSUMES binary inputs and compressed pubkeys +MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' +MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' +TESTNET_PRIVATE = b'\x04\x35\x83\x94' +TESTNET_PUBLIC = b'\x04\x35\x87\xCF' +PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] +PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] + +# BIP32 child key derivation + + +def raw_bip32_ckd(rawtuple, i): + vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple + i = int(i) + + if vbytes in PRIVATE: + priv = key + pub = utils.privtopub(key) + else: + pub = key + + if i >= 2**31: + if vbytes in PUBLIC: + raise Exception("Can't do private derivation on public key!") + hmac_res = hmac.new(chaincode, b'\x00' + priv[:32] + pyspecials.encode(i, 256, 4), + hashlib.sha512).digest() + else: + hmac_res = hmac.new(chaincode, pub + pyspecials.encode(i, 256, 4), hashlib.sha512).digest() + + if vbytes in PRIVATE: + newkey = utils.add_privkeys(hmac_res[:32] + B'\x01', priv) + fingerprint = utils.bin_hash160(utils.privtopub(key))[:4] + if vbytes in PUBLIC: + newkey = utils.add_pubkeys(utils.compress(utils.privtopub(hmac_res[:32])), key) + fingerprint = utils.bin_hash160(key)[:4] + + return (vbytes, depth + 1, fingerprint, i, hmac_res[32:], newkey) + + +def bip32_serialize(rawtuple): + vbytes, depth, fingerprint, i, chaincode, key = rawtuple + i = pyspecials.encode(i, 256, 4) + chaincode = pyspecials.encode(utils.hash_to_int(chaincode), 256, 32) + keydata = b'\x00' + key[:-1] if vbytes in PRIVATE else key + bindata = vbytes + pyspecials.from_int_to_byte(depth % 256) + fingerprint + i + \ + chaincode + keydata + return pyspecials.changebase(bindata + pyspecials.bin_dbl_sha256(bindata)[:4], 256, 58) + + +def bip32_deserialize(data): + dbin = pyspecials.changebase(data, 58, 256) + if pyspecials.bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: + raise Exception("Invalid checksum") + vbytes = dbin[0:4] + depth = pyspecials.from_byte_to_int(dbin[4]) + fingerprint = dbin[5:9] + i = pyspecials.decode(dbin[9:13], 256) + chaincode = dbin[13:45] + key = dbin[46:78] + b'\x01' if vbytes in PRIVATE else dbin[45:78] + return (vbytes, depth, fingerprint, i, chaincode, key) + + +def raw_bip32_privtopub(rawtuple): + vbytes, depth, fingerprint, i, chaincode, key = rawtuple + newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC + return (newvbytes, depth, fingerprint, i, chaincode, utils.privtopub(key)) + + +def bip32_privtopub(data): + return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) + + +def bip32_ckd(data, i): + return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) + + +def bip32_master_key(seed, vbytes=MAINNET_PRIVATE): + hmac_res = hmac.new(pyspecials.from_string_to_bytes("Bitcoin seed"), + pyspecials.from_string_to_bytes(seed), + hashlib.sha512).digest() + return bip32_serialize((vbytes, 0, b'\x00' * 4, 0, hmac_res[32:], hmac_res[:32] + b'\x01')) + + +def bip32_bin_extract_key(data): + return bip32_deserialize(data)[-1] + + +def bip32_extract_key(data): + return pyspecials.safe_hexlify(bip32_deserialize(data)[-1]) + +# Exploits the same vulnerability as above in Electrum wallets +# Takes a BIP32 pubkey and one of the child privkeys of its corresponding +# privkey and returns the BIP32 privkey associated with that pubkey + + +def raw_crack_bip32_privkey(parent_pub, priv): + vbytes, depth, fingerprint, i, chaincode, key = priv + pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub + i = int(i) + + if i >= 2**31: + raise Exception("Can't crack private derivation!") + + hmac_res = hmac.new(pchaincode, pkey + pyspecials.encode(i, 256, 4), hashlib.sha512).digest() + + pprivkey = utils.subtract_privkeys(key, hmac_res[:32] + b'\x01') + + newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE + return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) + + +def crack_bip32_privkey(parent_pub, priv): + dsppub = bip32_deserialize(parent_pub) + dspriv = bip32_deserialize(priv) + return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv)) + + +def coinvault_pub_to_bip32(*args): + if len(args) == 1: + args = args[0].split(' ') + vals = map(int, args[34:]) + I1 = ''.join(map(chr, vals[:33])) + I2 = ''.join(map(chr, vals[35:67])) + return bip32_serialize((MAINNET_PUBLIC, 0, b'\x00' * 4, 0, I2, I1)) + + +def coinvault_priv_to_bip32(*args): + if len(args) == 1: + args = args[0].split(' ') + vals = map(int, args[34:]) + I2 = ''.join(map(chr, vals[35:67])) + I3 = ''.join(map(chr, vals[72:104])) + return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00' * 4, 0, I2, I3 + b'\x01')) + + +def bip32_descend(*args): + if len(args) == 2 and isinstance(args[1], list): + key, path = args + else: + key, path = args[0], map(int, args[1:]) + + for p in path: + key = bip32_ckd(key, p) + + return bip32_extract_key(key) diff --git a/eth_account/mnemonic.py b/eth_account/hdaccount/mnemonic.py similarity index 96% rename from eth_account/mnemonic.py rename to eth_account/hdaccount/mnemonic.py index 8a3f8cb1..2b5a67d1 100644 --- a/eth_account/mnemonic.py +++ b/eth_account/hdaccount/mnemonic.py @@ -7,13 +7,12 @@ import random ''' -This library was included from pybitcointools +This library was initially included from pybitcointools https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/mnemonic.py -linter and style modifications have been applied ''' wordlist_english = open(os.path.join(os.path.dirname( - os.path.realpath(__file__)), 'english.txt'), 'r') + os.path.realpath(__file__)), 'bip39_english.txt'), 'r') def eint_to_bytes(entint, entbits): diff --git a/eth_account/hdaccount/pyspecials.py b/eth_account/hdaccount/pyspecials.py new file mode 100644 index 00000000..e5a9a698 --- /dev/null +++ b/eth_account/hdaccount/pyspecials.py @@ -0,0 +1,231 @@ +import binascii +import hashlib +import os +import re +import sys + +''' +This library was initially included from py2specials.py in pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/py3specials.py +''' + +if sys.version_info.major == 2: + string_types = (str, unicode) # noqa: F821 + string_or_bytes_types = string_types + int_types = (int, float, long) # noqa: F821 + + # Base switching + code_strings = { + 2: '01', + 10: '0123456789', + 16: '0123456789abcdef', + 32: 'abcdefghijklmnopqrstuvwxyz234567', + 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + 256: ''.join([chr(x) for x in range(256)]) + } + + def bin_dbl_sha256(s): + bytes_to_hash = from_string_to_bytes(s) + return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() + + def lpad(msg, symbol, length): + if len(msg) >= length: + return msg + + return symbol * (length - len(msg)) + msg + + def get_code_string(base): + if base in code_strings: + return code_strings[base] + else: + raise ValueError("Invalid base!") + + def changebase(string, frm, to, minlen=0): + if frm == to: + return lpad(string, get_code_string(frm)[0], minlen) + + return encode(decode(string, frm), to, minlen) + + def bin_to_b58check(inp, magicbyte=0): + if magicbyte == 0: + inp = '\x00' + inp + + while magicbyte > 0: + inp = chr(int(magicbyte % 256)) + inp + magicbyte //= 256 + + leadingzbytes = len(re.match('^\x00*', inp).group(0)) + checksum = bin_dbl_sha256(inp)[:4] + return '1' * leadingzbytes + changebase(inp + checksum, 256, 58) + + def bytes_to_hex_string(b): + return b.encode('hex') + + def safe_from_hex(s): + return s.decode('hex') + + def from_int_representation_to_bytes(a): + return str(a) + + def from_int_to_byte(a): + return chr(a) + + def from_byte_to_int(a): + return ord(a) + + def from_bytes_to_string(s): + return s + + def from_string_to_bytes(a): + return a + + def safe_hexlify(a): + return binascii.hexlify(a) + + def encode(val, base, minlen=0): + base, minlen = int(base), int(minlen) + code_string = get_code_string(base) + result = "" + + while val > 0: + result = code_string[val % base] + result + val //= base + + return code_string[0] * max(minlen - len(result), 0) + result + + def decode(string, base): + base = int(base) + code_string = get_code_string(base) + result = 0 + + if base == 16: + string = string.lower() + + while len(string) > 0: + result *= base + result += code_string.find(string[0]) + string = string[1:] + + return result + + def random_string(x): + return os.urandom(x) + +else: + string_types = (str) + string_or_bytes_types = (str, bytes) + int_types = (int, float) + # Base switching + code_strings = { + 2: '01', + 10: '0123456789', + 16: '0123456789abcdef', + 32: 'abcdefghijklmnopqrstuvwxyz234567', + 58: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', + 256: ''.join([chr(x) for x in range(256)]) + } + + def bin_dbl_sha256(s): + bytes_to_hash = from_string_to_bytes(s) + return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() + + def lpad(msg, symbol, length): + if len(msg) >= length: + return msg + return symbol * (length - len(msg)) + msg + + def get_code_string(base): + if base in code_strings: + return code_strings[base] + else: + raise ValueError("Invalid base!") + + def changebase(string, frm, to, minlen=0): + if frm == to: + return lpad(string, get_code_string(frm)[0], minlen) + return encode(decode(string, frm), to, minlen) + + def bin_to_b58check(inp, magicbyte=0): + if magicbyte == 0: + inp = from_int_to_byte(0) + inp + while magicbyte > 0: + inp = from_int_to_byte(magicbyte % 256) + inp + magicbyte //= 256 + + leadingzbytes = 0 + for x in inp: + if x != 0: + break + leadingzbytes += 1 + + checksum = bin_dbl_sha256(inp)[:4] + return '1' * leadingzbytes + changebase(inp + checksum, 256, 58) + + def bytes_to_hex_string(b): + if isinstance(b, str): + return b + + return ''.join('{:02x}'.format(y) for y in b) + + def safe_from_hex(s): + return bytes.fromhex(s) + + def from_int_representation_to_bytes(a): + return bytes(str(a), 'utf-8') + + def from_int_to_byte(a): + return bytes([a]) + + def from_byte_to_int(a): + return a + + def from_string_to_bytes(a): + return a if isinstance(a, bytes) else bytes(a, 'utf-8') + + def safe_hexlify(a): + return str(binascii.hexlify(a), 'utf-8') + + def encode(val, base, minlen=0): + base, minlen = int(base), int(minlen) + code_string = get_code_string(base) + result_bytes = bytes() + while val > 0: + curcode = code_string[val % base] + result_bytes = bytes([ord(curcode)]) + result_bytes + val //= base + + pad_size = minlen - len(result_bytes) + + padding_element = b'\x00' if base == 256 else b'1' \ + if base == 58 else b'0' + if (pad_size > 0): + result_bytes = padding_element * pad_size + result_bytes + + result_string = ''.join([chr(y) for y in result_bytes]) + result = result_bytes if base == 256 else result_string + + return result + + def decode(string, base): + if base == 256 and isinstance(string, str): + string = bytes(bytearray.fromhex(string)) + base = int(base) + code_string = get_code_string(base) + result = 0 + if base == 256: + def extract(d, cs): + return d + else: + def extract(d, cs): + return cs.find(d if isinstance(d, str) else chr(d)) + + if base == 16: + string = string.lower() + while len(string) > 0: + result *= base + result += extract(string[0], code_string) + string = string[1:] + return result + + def random_string(x): + return str(os.urandom(x)) diff --git a/eth_account/hdaccount/utils.py b/eth_account/hdaccount/utils.py new file mode 100644 index 00000000..a7eb2960 --- /dev/null +++ b/eth_account/hdaccount/utils.py @@ -0,0 +1,706 @@ +import binascii +import hashlib +import random +import re +from sys import ( + version_info, +) +import time + +import pyspecials + +# import base64 +# import hmac + +''' +This library is composite from main.py in pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py +''' + + +# Elliptic curve parameters (secp256k1) + +P = 2**256 - 2**32 - 977 +N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 +A = 0 +B = 7 +Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 +Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 +G = (Gx, Gy) + + +def change_curve(p, n, a, b, gx, gy): + global P, N, A, B, Gx, Gy, G + P, N, A, B, Gx, Gy = p, n, a, b, gx, gy + G = (Gx, Gy) + + +def getG(): + return G + +# Extended Euclidean Algorithm + + +def inv(a, n): + if a == 0: + return 0 + lm, hm = 1, 0 + low, high = a % n, n + while low > 1: + r = high // low + nm, new = hm - lm * r, high - low * r + lm, low, hm, high = nm, new, lm, low + return lm % n + +# JSON access (for pybtctool convenience) + + +def access(obj, prop): + if isinstance(obj, dict): + if prop in obj: + return obj[prop] + elif '.' in prop: + return obj[float(prop)] + else: + return obj[int(prop)] + else: + return obj[int(prop)] + + +def multiaccess(obj, prop): + return [access(o, prop) for o in obj] + + +def slice(obj, start=0, end=2**200): + return obj[int(start):int(end)] + + +def count(obj): + return len(obj) + + +_sum = sum + + +def sum(obj): + return _sum(obj) + + +def isinf(p): + return p[0] == 0 and p[1] == 0 + + +def to_jacobian(p): + o = (p[0], p[1], 1) + return o + + +def jacobian_double(p): + if not p[1]: + return (0, 0, 0) + ysq = (p[1] ** 2) % P + S = (4 * p[0] * ysq) % P + M = (3 * p[0] ** 2 + A * p[2] ** 4) % P + nx = (M**2 - 2 * S) % P + ny = (M * (S - nx) - 8 * ysq ** 2) % P + nz = (2 * p[1] * p[2]) % P + return (nx, ny, nz) + + +def jacobian_add(p, q): + if not p[1]: + return q + if not q[1]: + return p + U1 = (p[0] * q[2] ** 2) % P + U2 = (q[0] * p[2] ** 2) % P + S1 = (p[1] * q[2] ** 3) % P + S2 = (q[1] * p[2] ** 3) % P + if U1 == U2: + if S1 != S2: + return (0, 0, 1) + return jacobian_double(p) + H = U2 - U1 + R = S2 - S1 + H2 = (H * H) % P + H3 = (H * H2) % P + U1H2 = (U1 * H2) % P + nx = (R ** 2 - H3 - 2 * U1H2) % P + ny = (R * (U1H2 - nx) - S1 * H3) % P + nz = (H * p[2] * q[2]) % P + return (nx, ny, nz) + + +def from_jacobian(p): + z = inv(p[2], P) + return ((p[0] * z**2) % P, (p[1] * z**3) % P) + + +def jacobian_multiply(a, n): + if a[1] == 0 or n == 0: + return (0, 0, 1) + + if n == 1: + return a + + if n < 0 or n >= N: + return jacobian_multiply(a, n % N) + + if (n % 2) == 0: + return jacobian_double(jacobian_multiply(a, n // 2)) + + if (n % 2) == 1: + return jacobian_add(jacobian_double(jacobian_multiply(a, n // 2)), a) + + +def fast_multiply(a, n): + return from_jacobian(jacobian_multiply(to_jacobian(a), n)) + + +def fast_add(a, b): + return from_jacobian(jacobian_add(to_jacobian(a), to_jacobian(b))) + +# Functions for handling pubkey and privkey formats + + +def get_pubkey_format(pub): + if version_info.major == 2: + two = '\x02' + three = '\x03' + four = '\x04' + else: + two = 2 + three = 3 + four = 4 + + if isinstance(pub, (tuple, list)): + return 'decimal' + elif len(pub) == 65 and pub[0] == four: + return 'bin' + elif len(pub) == 130 and pub[0:2] == '04': + return 'hex' + elif len(pub) == 33 and pub[0] in [two, three]: + return 'bin_compressed' + elif len(pub) == 66 and pub[0:2] in ['02', '03']: + return 'hex_compressed' + elif len(pub) == 64: + return 'bin_electrum' + elif len(pub) == 128: + return 'hex_electrum' + else: + raise Exception("Pubkey not in recognized format") + + +def encode_pubkey(pub, formt): + if not isinstance(pub, (tuple, list)): + pub = decode_pubkey(pub) + + if formt == 'decimal': + return pub + elif formt == 'bin': + return b'\x04' + pyspecials.encode(pub[0], 256, 32) + pyspecials.encode(pub[1], 256, 32) + elif formt == 'bin_compressed': + return pyspecials.from_int_to_byte(2 + (pub[1] % 2)) + pyspecials.encode(pub[0], 256, 32) + elif formt == 'hex': + return '04' + pyspecials.encode(pub[0], 16, 64) + pyspecials.encode(pub[1], 16, 64) + elif formt == 'hex_compressed': + return '0' + str(2 + (pub[1] % 2)) + pyspecials.encode(pub[0], 16, 64) + elif formt == 'bin_electrum': + return pyspecials.encode(pub[0], 256, 32) + pyspecials.encode(pub[1], 256, 32) + elif formt == 'hex_electrum': + return pyspecials.encode(pub[0], 16, 64) + pyspecials.encode(pub[1], 16, 64) + else: + raise Exception("Invalid format!") + + +def decode_pubkey(pub, formt=None): + if not formt: + formt = get_pubkey_format(pub) + + if formt == 'decimal': + return pub + elif formt == 'bin': + return (pyspecials.decode(pub[1:33], 256), pyspecials.decode(pub[33:65], 256)) + elif formt == 'bin_compressed': + x = pyspecials.decode(pub[1:33], 256) + beta = pow(int(x * x * x + A * x + B), int((P + 1) // 4), int(P)) + y = (P - beta) if ((beta + pyspecials.from_byte_to_int(pub[0])) % 2) else beta + return (x, y) + elif formt == 'hex': + return (pyspecials.decode(pub[2:66], 16), pyspecials.decode(pub[66:130], 16)) + elif formt == 'hex_compressed': + return decode_pubkey(pyspecials.safe_from_hex(pub), 'bin_compressed') + elif formt == 'bin_electrum': + return (pyspecials.decode(pub[:32], 256), pyspecials.decode(pub[32:64], 256)) + elif formt == 'hex_electrum': + return (pyspecials.decode(pub[:64], 16), pyspecials.decode(pub[64:128], 16)) + else: + raise Exception("Invalid format!") + + +def get_privkey_format(priv): + if isinstance(priv, pyspecials.int_types): + return 'decimal' + elif len(priv) == 32: + return 'bin' + elif len(priv) == 33: + return 'bin_compressed' + elif len(priv) == 64: + return 'hex' + elif len(priv) == 66: + return 'hex_compressed' + else: + bin_p = b58check_to_bin(priv) + + if len(bin_p) == 32: + return 'wif' + elif len(bin_p) == 33: + return 'wif_compressed' + else: + raise Exception("WIF does not represent privkey") + + +def encode_privkey(priv, formt, vbyte=0): + if not isinstance(priv, pyspecials.int_types): + return encode_privkey(decode_privkey(priv), formt, vbyte) + + if formt == 'decimal': + return priv + elif formt == 'bin': + return pyspecials.encode(priv, 256, 32) + elif formt == 'bin_compressed': + return pyspecials.encode(priv, 256, 32) + b'\x01' + elif formt == 'hex': + return pyspecials.encode(priv, 16, 64) + elif formt == 'hex_compressed': + return pyspecials.encode(priv, 16, 64) + '01' + elif formt == 'wif': + return pyspecials.bin_to_b58check(pyspecials.encode(priv, 256, 32), + 128 + int(vbyte)) + elif formt == 'wif_compressed': + return pyspecials.bin_to_b58check(pyspecials.encode(priv, 256, 32) + + b'\x01', 128 + int(vbyte)) + else: + raise Exception("Invalid format!") + + +def decode_privkey(priv, formt=None): + if not formt: + formt = get_privkey_format(priv) + + if formt == 'decimal': + return priv + elif formt == 'bin': + return pyspecials.decode(priv, 256) + elif formt == 'bin_compressed': + return pyspecials.decode(priv[:32], 256) + elif formt == 'hex': + return pyspecials.decode(priv, 16) + elif formt == 'hex_compressed': + return pyspecials.decode(priv[:64], 16) + elif formt == 'wif': + return pyspecials.decode(b58check_to_bin(priv), 256) + elif formt == 'wif_compressed': + return pyspecials.decode(b58check_to_bin(priv)[:32], 256) + else: + raise Exception("WIF does not represent privkey") + + +def add_pubkeys(p1, p2): + f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) + return encode_pubkey(fast_add(decode_pubkey(p1, f1), decode_pubkey(p2, f2)), f1) + + +def add_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + return encode_privkey((decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1) + + +def mul_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + return encode_privkey((decode_privkey(p1, f1) * decode_privkey(p2, f2)) % N, f1) + + +def multiply(pubkey, privkey): + f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey) + pubkey, privkey = decode_pubkey(pubkey, f1), decode_privkey(privkey, f2) + + # http://safecurves.cr.yp.to/twist.html + if not isinf(pubkey) and (pubkey[0] ** 3 + B - pubkey[1] * pubkey[1]) % P != 0: + raise Exception("Point not on curve") + + return encode_pubkey(fast_multiply(pubkey, privkey), f1) + + +def divide(pubkey, privkey): + factor = inv(decode_privkey(privkey), N) + return multiply(pubkey, factor) + + +def compress(pubkey): + f = get_pubkey_format(pubkey) + + if 'compressed' in f: + return pubkey + elif f == 'bin': + return encode_pubkey(decode_pubkey(pubkey, f), 'bin_compressed') + elif f == 'hex' or f == 'decimal': + return encode_pubkey(decode_pubkey(pubkey, f), 'hex_compressed') + + +def decompress(pubkey): + f = get_pubkey_format(pubkey) + + if 'compressed' not in f: + return pubkey + elif f == 'bin_compressed': + return encode_pubkey(decode_pubkey(pubkey, f), 'bin') + elif f == 'hex_compressed' or f == 'decimal': + return encode_pubkey(decode_pubkey(pubkey, f), 'hex') + + +def privkey_to_pubkey(privkey): + f = get_privkey_format(privkey) + privkey = decode_privkey(privkey, f) + if privkey >= N: + raise Exception("Invalid privkey") + if f in ['bin', 'bin_compressed', 'hex', 'hex_compressed', 'decimal']: + return encode_pubkey(fast_multiply(G, privkey), f) + else: + return encode_pubkey(fast_multiply(G, privkey), f.replace('wif', 'hex')) + + +privtopub = privkey_to_pubkey + + +''' +def privkey_to_address(priv, magicbyte=0): + return pubkey_to_address(privkey_to_pubkey(priv), magicbyte) + + +privtoaddr = privkey_to_address +''' + + +def neg_pubkey(pubkey): + f = get_pubkey_format(pubkey) + pubkey = decode_pubkey(pubkey, f) + return encode_pubkey((pubkey[0], (P - pubkey[1]) % P), f) + + +def neg_privkey(privkey): + f = get_privkey_format(privkey) + privkey = decode_privkey(privkey, f) + return encode_privkey((N - privkey) % N, f) + + +def subtract_pubkeys(p1, p2): + f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) + k2 = decode_pubkey(p2, f2) + return encode_pubkey(fast_add(decode_pubkey(p1, f1), (k2[0], (P - k2[1]) % P)), f1) + + +def subtract_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + k2 = decode_privkey(p2, f2) + return encode_privkey((decode_privkey(p1, f1) - k2) % N, f1) + + +# Hashes + +def bin_hash160(string): + intermed = hashlib.sha256(string).digest() + digest = '' + + ''' + try: + digest = hashlib.new('ripemd160', intermed).digest() + except Exception: + digest = RIPEMD160(intermed).digest() + ''' + + digest = hashlib.new('ripemd160', intermed).digest() + + return digest + + +def hash160(string): + return pyspecials.safe_hexlify(bin_hash160(string)) + + +def bin_sha256(string): + binary_data = string if isinstance(string, bytes) else bytes(string, 'utf-8') + return hashlib.sha256(binary_data).digest() + + +def sha256(string): + return pyspecials.bytes_to_hex_string(bin_sha256(string)) + + +''' +def bin_ripemd160(string): + try: + digest = hashlib.new('ripemd160', string).digest() + except Exception: + digest = RIPEMD160(string).digest() + return digest + + +def ripemd160(string): + return safe_hexlify(bin_ripemd160(string)) +''' + + +def bin_dbl_sha256(s): + bytes_to_hash = pyspecials.from_string_to_bytes(s) + return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() + + +def dbl_sha256(string): + return pyspecials.safe_hexlify(bin_dbl_sha256(string)) + + +def bin_slowsha(string): + string = pyspecials.from_string_to_bytes(string) + orig_input = string + for i in range(100000): + string = hashlib.sha256(string + orig_input).digest() + return string + + +def slowsha(string): + return pyspecials.safe_hexlify(bin_slowsha(string)) + + +def hash_to_int(x): + if len(x) in [40, 64]: + return pyspecials.decode(x, 16) + return pyspecials.decode(x, 256) + + +def num_to_var_int(x): + x = int(x) + + if x < 253: + return pyspecials.from_int_to_byte(x) + elif x < 65536: + return pyspecials.from_int_to_byte(253) + pyspecials.encode(x, 256, 2)[::-1] + elif x < 4294967296: + return pyspecials.from_int_to_byte(254) + pyspecials.encode(x, 256, 4)[::-1] + else: + return pyspecials.from_int_to_byte(255) + pyspecials.encode(x, 256, 8)[::-1] + + +# WTF, Electrum? +def electrum_sig_hash(message): + padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(message))\ + + pyspecials.from_string_to_bytes(message) + return bin_dbl_sha256(padded) + + +def random_key(): + # Gotta be secure after that java.SecureRandom fiasco... + entropy = pyspecials.random_string(32) \ + + str(random.randrange(2**256)) \ + + str(int(time.time() * 1000000)) + return sha256(entropy) + + +def random_electrum_seed(): + # entropy = os.urandom(32) \ # fails in Python 3, hence copied from random_key() + entropy = pyspecials.random_string(32) \ + + str(random.randrange(2**256)) \ + + str(int(time.time() * 1000000)) + return sha256(entropy)[:32] + + +# Encodings + + +def b58check_to_bin(inp): + leadingzbytes = len(re.match('^1*', inp).group(0)) + data = b'\x00' * leadingzbytes + pyspecials.changebase(inp, 58, 256) + assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] + return data[1:-4] + + +def get_version_byte(inp): + leadingzbytes = len(re.match('^1*', inp).group(0)) + data = b'\x00' * leadingzbytes + pyspecials.changebase(inp, 58, 256) + assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] + return ord(data[0]) + + +def hex_to_b58check(inp, magicbyte=0): + return pyspecials.bin_to_b58check(binascii.unhexlify(inp), magicbyte) + + +def b58check_to_hex(inp): + return pyspecials.safe_hexlify(b58check_to_bin(inp)) + + +''' +def pubkey_to_address(pubkey, magicbyte=0): + if isinstance(pubkey, (list, tuple)): + pubkey = encode_pubkey(pubkey, 'bin') + if len(pubkey) in [66, 130]: + return pyspecials.bin_to_b58check( + bin_hash160(binascii.unhexlify(pubkey)), magicbyte) + return bin_to_b58check(bin_hash160(pubkey), magicbyte) + + +pubtoaddr = pubkey_to_address +''' + + +def is_privkey(priv): + try: + get_privkey_format(priv) + return True + except Exception: + return False + + +def is_pubkey(pubkey): + try: + get_pubkey_format(pubkey) + return True + except Exception: + return False + + +def is_address(addr): + ADDR_RE = re.compile("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$") + return bool(ADDR_RE.match(addr)) + + +# EDCSA + +''' +def encode_sig(v, r, s): + vb, rb, sb = pyspecials.from_int_to_byte(v), pyspecials.encode(r, 256), \ + pyspecials.encode(s, 256) + + result = base64.b64encode(vb + b'\x00' * (32 - len(rb)) + rb + + b'\x00' * (32 - len(sb)) + sb) + return result if version_info.major == 2 else str(result, 'utf-8') + + +def decode_sig(sig): + bytez = base64.b64decode(sig) + return pyspecials.from_byte_to_int(bytez[0]), pyspecials.decode(bytez[1:33], 256), \ + pyspecials.decode(bytez[33:], 256) + +# https://tools.ietf.org/html/rfc6979#section-3.2 + + +def deterministic_generate_k(msghash, priv): + v = b'\x01' * 32 + k = b'\x00' * 32 + priv = encode_privkey(priv, 'bin') + msghash = encode(hash_to_int(msghash), 256, 32) + k = hmac.new(k, v+b'\x00' + priv + msghash, hashlib.sha256).digest() + v = hmac.new(k, v, hashlib.sha256).digest() + k = hmac.new(k, v+b'\x01' + priv + msghash, hashlib.sha256).digest() + v = hmac.new(k, v, hashlib.sha256).digest() + return pyspecials.decode(hmac.new(k, v, hashlib.sha256).digest(), 256) + + +def ecdsa_raw_sign(msghash, priv): + + z = hash_to_int(msghash) + k = deterministic_generate_k(msghash, priv) + + r, y = fast_multiply(G, k) + s = inv(k, N) * (z + r*decode_privkey(priv)) % N + + v, r, s = 27 + ((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s + + if 'compressed' in get_privkey_format(priv): + v += 4 + + return v, r, s + + +def ecdsa_sign(msg, priv): + v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) + sig = encode_sig(v, r, s) + + assert ecdsa_verify(msg, sig, privtopub(priv)), \ + "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) + + return sig + + +def ecdsa_raw_verify(msghash, vrs, pub): + v, r, s = vrs + if not (27 <= v <= 34): + return False + + w = inv(s, N) + z = hash_to_int(msghash) + + u1, u2 = z * w % N, r * w % N + x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) + return bool(r == x and (r % N) and (s % N)) + + +# For BitcoinCore, (msg = addr or msg = "") be default +def ecdsa_verify_addr(msg, sig, addr): + assert is_address(addr) + Q = ecdsa_recover(msg, sig) + magic = get_version_byte(addr) + return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) + + +def ecdsa_verify(msg, sig, pub): + if is_address(pub): + return ecdsa_verify_addr(msg, sig, pub) + return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) + + +def ecdsa_raw_recover(msghash, vrs): + v, r, s = vrs + if not (27 <= v <= 34): + raise ValueError("%d must in range 27-31" % v) + x = r + xcubedaxb = (x*x*x+A*x+B) % P + beta = pow(xcubedaxb, (P+1)//4, P) + y = beta if v % 2 ^ beta % 2 else (P - beta) + # If xcubedaxb is not a quadratic residue, then r cannot be the x coord + # for a point on the curve, and so the sig is invalid + if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): + return False + z = hash_to_int(msghash) + Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) + XY = jacobian_multiply((x, y, 1), s) + Qr = jacobian_add(Gz, XY) + Q = jacobian_multiply(Qr, inv(r, N)) + Q = from_jacobian(Q) + + # if ecdsa_raw_verify(msghash, vrs, Q): + return Q + # return False + + +def ecdsa_recover(msg, sig): + v,r,s = decode_sig(sig) + Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) + return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') + +''' + + +# add/subtract +def add(p1, p2): + if is_privkey(p1): + return add_privkeys(p1, p2) + else: + return add_pubkeys(p1, p2) + + +def subtract(p1, p2): + if is_privkey(p1): + return subtract_privkeys(p1, p2) + else: + return subtract_pubkeys(p1, p2) diff --git a/eth_account/utils.py b/eth_account/utils.py new file mode 100644 index 00000000..cdbaab2a --- /dev/null +++ b/eth_account/utils.py @@ -0,0 +1,686 @@ +from pyspecials import * +import binascii +import hashlib +import re +import base64 +import time +import random +import hmac + +''' +This library is composite from main.py in pybitcointools +https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py +''' + + +# Elliptic curve parameters (secp256k1) + +P = 2**256 - 2**32 - 977 +N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 +A = 0 +B = 7 +Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 +Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 +G = (Gx, Gy) + + +def change_curve(p, n, a, b, gx, gy): + global P, N, A, B, Gx, Gy, G + P, N, A, B, Gx, Gy = p, n, a, b, gx, gy + G = (Gx, Gy) + + +def getG(): + return G + +# Extended Euclidean Algorithm + + +def inv(a, n): + if a == 0: + return 0 + lm, hm = 1, 0 + low, high = a % n, n + while low > 1: + r = high // low + nm, new = hm - lm * r, high - low * r + lm, low, hm, high = nm, new, lm, low + return lm % n + +# JSON access (for pybtctool convenience) + + +def access(obj, prop): + if isinstance(obj, dict): + if prop in obj: + return obj[prop] + elif '.' in prop: + return obj[float(prop)] + else: + return obj[int(prop)] + else: + return obj[int(prop)] + + +def multiaccess(obj, prop): + return [access(o, prop) for o in obj] + + +def slice(obj, start=0, end=2**200): + return obj[int(start):int(end)] + + +def count(obj): + return len(obj) + + +_sum = sum + + +def sum(obj): + return _sum(obj) + + +def isinf(p): + return p[0] == 0 and p[1] == 0 + + +def to_jacobian(p): + o = (p[0], p[1], 1) + return o + + +def jacobian_double(p): + if not p[1]: + return (0, 0, 0) + ysq = (p[1] ** 2) % P + S = (4 * p[0] * ysq) % P + M = (3 * p[0] ** 2 + A * p[2] ** 4) % P + nx = (M**2 - 2 * S) % P + ny = (M * (S - nx) - 8 * ysq ** 2) % P + nz = (2 * p[1] * p[2]) % P + return (nx, ny, nz) + + +def jacobian_add(p, q): + if not p[1]: + return q + if not q[1]: + return p + U1 = (p[0] * q[2] ** 2) % P + U2 = (q[0] * p[2] ** 2) % P + S1 = (p[1] * q[2] ** 3) % P + S2 = (q[1] * p[2] ** 3) % P + if U1 == U2: + if S1 != S2: + return (0, 0, 1) + return jacobian_double(p) + H = U2 - U1 + R = S2 - S1 + H2 = (H * H) % P + H3 = (H * H2) % P + U1H2 = (U1 * H2) % P + nx = (R ** 2 - H3 - 2 * U1H2) % P + ny = (R * (U1H2 - nx) - S1 * H3) % P + nz = (H * p[2] * q[2]) % P + return (nx, ny, nz) + + +def from_jacobian(p): + z = inv(p[2], P) + return ((p[0] * z**2) % P, (p[1] * z**3) % P) + + +def jacobian_multiply(a, n): + if a[1] == 0 or n == 0: + return (0, 0, 1) + + if n == 1: + return a + + if n < 0 or n >= N: + return jacobian_multiply(a, n % N) + + if (n % 2) == 0: + return jacobian_double(jacobian_multiply(a, n // 2)) + + if (n % 2) == 1: + return jacobian_add(jacobian_double(jacobian_multiply(a, n // 2)), a) + + +def fast_multiply(a, n): + return from_jacobian(jacobian_multiply(to_jacobian(a), n)) + + +def fast_add(a, b): + return from_jacobian(jacobian_add(to_jacobian(a), to_jacobian(b))) + +# Functions for handling pubkey and privkey formats + + +def get_pubkey_format(pub): + if is_python2: + two = '\x02' + three = '\x03' + four = '\x04' + else: + two = 2 + three = 3 + four = 4 + + if isinstance(pub, (tuple, list)): + return 'decimal' + elif len(pub) == 65 and pub[0] == four: + return 'bin' + elif len(pub) == 130 and pub[0:2] == '04': + return 'hex' + elif len(pub) == 33 and pub[0] in [two, three]: + return 'bin_compressed' + elif len(pub) == 66 and pub[0:2] in ['02', '03']: + return 'hex_compressed' + elif len(pub) == 64: + return 'bin_electrum' + elif len(pub) == 128: + return 'hex_electrum' + else: + raise Exception("Pubkey not in recognized format") + + +def encode_pubkey(pub, formt): + if not isinstance(pub, (tuple, list)): + pub = decode_pubkey(pub) + + if formt == 'decimal': + return pub + elif formt == 'bin': + return b'\x04' + encode(pub[0], 256, 32) + encode(pub[1], 256, 32) + elif formt == 'bin_compressed': + return from_int_to_byte(2 + (pub[1] % 2)) + encode(pub[0], 256, 32) + elif formt == 'hex': + return '04' + encode(pub[0], 16, 64) + encode(pub[1], 16, 64) + elif formt == 'hex_compressed': + return '0' + str(2 + (pub[1] % 2)) + encode(pub[0], 16, 64) + elif formt == 'bin_electrum': + return encode(pub[0], 256, 32) + encode(pub[1], 256, 32) + elif formt == 'hex_electrum': + return encode(pub[0], 16, 64) + encode(pub[1], 16, 64) + else: + raise Exception("Invalid format!") + + +def decode_pubkey(pub, formt=None): + if not formt: + formt = get_pubkey_format(pub) + + if formt == 'decimal': + return pub + elif formt == 'bin': + return (decode(pub[1:33], 256), decode(pub[33:65], 256)) + elif formt == 'bin_compressed': + x = decode(pub[1:33], 256) + beta = pow(int(x * x * x + A * x + B), int((P + 1) // 4), int(P)) + y = (P - beta) if ((beta + from_byte_to_int(pub[0])) % 2) else beta + return (x, y) + elif formt == 'hex': + return (decode(pub[2:66], 16), decode(pub[66:130], 16)) + elif formt == 'hex_compressed': + return decode_pubkey(safe_from_hex(pub), 'bin_compressed') + elif formt == 'bin_electrum': + return (decode(pub[:32], 256), decode(pub[32:64], 256)) + elif formt == 'hex_electrum': + return (decode(pub[:64], 16), decode(pub[64:128], 16)) + else: + raise Exception("Invalid format!") + + +def get_privkey_format(priv): + if isinstance(priv, int_types): + return 'decimal' + elif len(priv) == 32: + return 'bin' + elif len(priv) == 33: + return 'bin_compressed' + elif len(priv) == 64: + return 'hex' + elif len(priv) == 66: + return 'hex_compressed' + else: + bin_p = b58check_to_bin(priv) + + if len(bin_p) == 32: + return 'wif' + elif len(bin_p) == 33: + return 'wif_compressed' + else: + raise Exception("WIF does not represent privkey") + + +def encode_privkey(priv, formt, vbyte=0): + if not isinstance(priv, int_types): + return encode_privkey(decode_privkey(priv), formt, vbyte) + + if formt == 'decimal': + return priv + elif formt == 'bin': + return encode(priv, 256, 32) + elif formt == 'bin_compressed': + return encode(priv, 256, 32)+b'\x01' + elif formt == 'hex': + return encode(priv, 16, 64) + elif formt == 'hex_compressed': + return encode(priv, 16, 64)+'01' + elif formt == 'wif': + return bin_to_b58check(encode(priv, 256, 32), 128+int(vbyte)) + elif formt == 'wif_compressed': + return bin_to_b58check(encode(priv, 256, 32)+b'\x01', 128+int(vbyte)) + else: + raise Exception("Invalid format!") + +def decode_privkey(priv,formt=None): + if not formt: + formt = get_privkey_format(priv) + + if formt == 'decimal': + return priv + elif formt == 'bin': + return decode(priv, 256) + elif formt == 'bin_compressed': + return decode(priv[:32], 256) + elif formt == 'hex': + return decode(priv, 16) + elif formt == 'hex_compressed': + return decode(priv[:64], 16) + elif formt == 'wif': + return decode(b58check_to_bin(priv),256) + elif formt == 'wif_compressed': + return decode(b58check_to_bin(priv)[:32],256) + else: + raise Exception("WIF does not represent privkey") + + +def add_pubkeys(p1, p2): + f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) + return encode_pubkey(fast_add(decode_pubkey(p1, f1), decode_pubkey(p2, f2)), f1) + + +def add_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + return encode_privkey((decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1) + + +def mul_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + return encode_privkey((decode_privkey(p1, f1) * decode_privkey(p2, f2)) % N, f1) + + +def multiply(pubkey, privkey): + f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey) + pubkey, privkey = decode_pubkey(pubkey, f1), decode_privkey(privkey, f2) + + # http://safecurves.cr.yp.to/twist.html + if not isinf(pubkey) and (pubkey[0] ** 3 + B - pubkey[1] * pubkey[1]) % P != 0: + raise Exception("Point not on curve") + + return encode_pubkey(fast_multiply(pubkey, privkey), f1) + + +def divide(pubkey, privkey): + factor = inv(decode_privkey(privkey), N) + return multiply(pubkey, factor) + + +def compress(pubkey): + f = get_pubkey_format(pubkey) + + if 'compressed' in f: + return pubkey + elif f == 'bin': + return encode_pubkey(decode_pubkey(pubkey, f), 'bin_compressed') + elif f == 'hex' or f == 'decimal': + return encode_pubkey(decode_pubkey(pubkey, f), 'hex_compressed') + + +def decompress(pubkey): + f = get_pubkey_format(pubkey) + + if 'compressed' not in f: + return pubkey + elif f == 'bin_compressed': + return encode_pubkey(decode_pubkey(pubkey, f), 'bin') + elif f == 'hex_compressed' or f == 'decimal': + return encode_pubkey(decode_pubkey(pubkey, f), 'hex') + + +def privkey_to_pubkey(privkey): + f = get_privkey_format(privkey) + privkey = decode_privkey(privkey, f) + if privkey >= N: + raise Exception("Invalid privkey") + if f in ['bin', 'bin_compressed', 'hex', 'hex_compressed', 'decimal']: + return encode_pubkey(fast_multiply(G, privkey), f) + else: + return encode_pubkey(fast_multiply(G, privkey), f.replace('wif', 'hex')) + + +privtopub = privkey_to_pubkey + + +def privkey_to_address(priv, magicbyte=0): + return pubkey_to_address(privkey_to_pubkey(priv), magicbyte) + + +privtoaddr = privkey_to_address + + +def neg_pubkey(pubkey): + f = get_pubkey_format(pubkey) + pubkey = decode_pubkey(pubkey, f) + return encode_pubkey((pubkey[0], (P - pubkey[1]) % P), f) + + +def neg_privkey(privkey): + f = get_privkey_format(privkey) + privkey = decode_privkey(privkey, f) + return encode_privkey((N - privkey) % N, f) + + +def subtract_pubkeys(p1, p2): + f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) + k2 = decode_pubkey(p2, f2) + return encode_pubkey(fast_add(decode_pubkey(p1, f1), (k2[0], (P - k2[1]) % P)), f1) + + +def subtract_privkeys(p1, p2): + f1, f2 = get_privkey_format(p1), get_privkey_format(p2) + k2 = decode_privkey(p2, f2) + return encode_privkey((decode_privkey(p1, f1) - k2) % N, f1) + + +# Hashes + + +def bin_hash160(string): + intermed = hashlib.sha256(string).digest() + digest = '' + + try: + digest = hashlib.new('ripemd160', intermed).digest() + except Exception: + digest = RIPEMD160(intermed).digest() + + return digest + + +def hash160(string): + return safe_hexlify(bin_hash160(string)) + + +def bin_sha256(string): + binary_data = string if isinstance(string, bytes) else bytes(string, 'utf-8') + return hashlib.sha256(binary_data).digest() + + +def sha256(string): + return bytes_to_hex_string(bin_sha256(string)) + + +def bin_ripemd160(string): + try: + digest = hashlib.new('ripemd160', string).digest() + except Exception: + digest = RIPEMD160(string).digest() + return digest + + +def ripemd160(string): + return safe_hexlify(bin_ripemd160(string)) + + +def bin_dbl_sha256(s): + bytes_to_hash = from_string_to_bytes(s) + return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() + + +def dbl_sha256(string): + return safe_hexlify(bin_dbl_sha256(string)) + + +def bin_slowsha(string): + string = from_string_to_bytes(string) + orig_input = string + for i in range(100000): + string = hashlib.sha256(string + orig_input).digest() + return string + + +def slowsha(string): + return safe_hexlify(bin_slowsha(string)) + + +def hash_to_int(x): + if len(x) in [40, 64]: + return decode(x, 16) + return decode(x, 256) + + +def num_to_var_int(x): + x = int(x) + + if x < 253: + return from_int_to_byte(x) + elif x < 65536: + return from_int_to_byte(253)+encode(x, 256, 2)[::-1] + elif x < 4294967296: + return from_int_to_byte(254) + encode(x, 256, 4)[::-1] + else: + return from_int_to_byte(255) + encode(x, 256, 8)[::-1] + + +# WTF, Electrum? +def electrum_sig_hash(message): + padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(message))\ + + from_string_to_bytes(message) + return bin_dbl_sha256(padded) + + +def random_key(): + # Gotta be secure after that java.SecureRandom fiasco... + entropy = random_string(32) \ + + str(random.randrange(2**256)) \ + + str(int(time.time() * 1000000)) + return sha256(entropy) + + +def random_electrum_seed(): + # entropy = os.urandom(32) \ # fails in Python 3, hence copied from random_key() + entropy = random_string(32) \ + + str(random.randrange(2**256)) \ + + str(int(time.time() * 1000000)) + return sha256(entropy)[:32] + + +# Encodings + + +def b58check_to_bin(inp): + leadingzbytes = len(re.match('^1*', inp).group(0)) + data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) + assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] + return data[1:-4] + + +def get_version_byte(inp): + leadingzbytes = len(re.match('^1*', inp).group(0)) + data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) + assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] + return ord(data[0]) + + +def hex_to_b58check(inp, magicbyte=0): + return bin_to_b58check(binascii.unhexlify(inp), magicbyte) + + +def b58check_to_hex(inp): + return safe_hexlify(b58check_to_bin(inp)) + + +def pubkey_to_address(pubkey, magicbyte=0): + if isinstance(pubkey, (list, tuple)): + pubkey = encode_pubkey(pubkey, 'bin') + if len(pubkey) in [66, 130]: + return bin_to_b58check( + bin_hash160(binascii.unhexlify(pubkey)), magicbyte) + return bin_to_b58check(bin_hash160(pubkey), magicbyte) + + +pubtoaddr = pubkey_to_address + + +def is_privkey(priv): + try: + get_privkey_format(priv) + return True + except Exception: + return False + + +def is_pubkey(pubkey): + try: + get_pubkey_format(pubkey) + return True + except Exception: + return False + + +def is_address(addr): + ADDR_RE = re.compile("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$") + return bool(ADDR_RE.match(addr)) + + +# EDCSA + + +def encode_sig(v, r, s): + vb, rb, sb = from_int_to_byte(v), encode(r, 256), encode(s, 256) + result = base64.b64encode(vb + b'\x00' * (32 - len(rb)) + rb + + b'\x00' * (32 - len(sb)) + sb) + return result if is_python2 else str(result, 'utf-8') + + +def decode_sig(sig): + bytez = base64.b64decode(sig) + return from_byte_to_int(bytez[0]), decode(bytez[1:33], 256), decode(bytez[33:], 256) + +# https://tools.ietf.org/html/rfc6979#section-3.2 + + +''' +def deterministic_generate_k(msghash, priv): + v = b'\x01' * 32 + k = b'\x00' * 32 + priv = encode_privkey(priv, 'bin') + msghash = encode(hash_to_int(msghash), 256, 32) + k = hmac.new(k, v+b'\x00' + priv + msghash, hashlib.sha256).digest() + v = hmac.new(k, v, hashlib.sha256).digest() + k = hmac.new(k, v+b'\x01' + priv + msghash, hashlib.sha256).digest() + v = hmac.new(k, v, hashlib.sha256).digest() + return decode(hmac.new(k, v, hashlib.sha256).digest(), 256) + + +def ecdsa_raw_sign(msghash, priv): + + z = hash_to_int(msghash) + k = deterministic_generate_k(msghash, priv) + + r, y = fast_multiply(G, k) + s = inv(k, N) * (z + r*decode_privkey(priv)) % N + + v, r, s = 27 + ((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s + + if 'compressed' in get_privkey_format(priv): + v += 4 + + return v, r, s + + +def ecdsa_sign(msg, priv): + v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) + sig = encode_sig(v, r, s) + + assert ecdsa_verify(msg, sig, privtopub(priv)), \ + "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) + + return sig + + +def ecdsa_raw_verify(msghash, vrs, pub): + v, r, s = vrs + if not (27 <= v <= 34): + return False + + w = inv(s, N) + z = hash_to_int(msghash) + + u1, u2 = z * w % N, r * w % N + x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) + return bool(r == x and (r % N) and (s % N)) + + +# For BitcoinCore, (msg = addr or msg = "") be default +def ecdsa_verify_addr(msg, sig, addr): + assert is_address(addr) + Q = ecdsa_recover(msg, sig) + magic = get_version_byte(addr) + return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) + + +def ecdsa_verify(msg, sig, pub): + if is_address(pub): + return ecdsa_verify_addr(msg, sig, pub) + return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) + + +def ecdsa_raw_recover(msghash, vrs): + v, r, s = vrs + if not (27 <= v <= 34): + raise ValueError("%d must in range 27-31" % v) + x = r + xcubedaxb = (x*x*x+A*x+B) % P + beta = pow(xcubedaxb, (P+1)//4, P) + y = beta if v % 2 ^ beta % 2 else (P - beta) + # If xcubedaxb is not a quadratic residue, then r cannot be the x coord + # for a point on the curve, and so the sig is invalid + if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): + return False + z = hash_to_int(msghash) + Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) + XY = jacobian_multiply((x, y, 1), s) + Qr = jacobian_add(Gz, XY) + Q = jacobian_multiply(Qr, inv(r, N)) + Q = from_jacobian(Q) + + # if ecdsa_raw_verify(msghash, vrs, Q): + return Q + # return False + + +def ecdsa_recover(msg, sig): + v,r,s = decode_sig(sig) + Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) + return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') + +''' + + +# add/subtract +def add(p1, p2): + if is_privkey(p1): + return add_privkeys(p1, p2) + else: + return add_pubkeys(p1, p2) + +def subtract(p1, p2): + if is_privkey(p1): + return subtract_privkeys(p1, p2) + else: + return subtract_pubkeys(p1, p2) From 260e0ca4aabdad32bd25229990daf1e1b2069002 Mon Sep 17 00:00:00 2001 From: sea212 Date: Fri, 31 Aug 2018 18:17:16 +0200 Subject: [PATCH 11/27] Forgot to remove deprecated files --- eth_account/deterministic.py | 210 ----------- eth_account/utils.py | 686 ----------------------------------- 2 files changed, 896 deletions(-) delete mode 100644 eth_account/deterministic.py delete mode 100644 eth_account/utils.py diff --git a/eth_account/deterministic.py b/eth_account/deterministic.py deleted file mode 100644 index c10fbab3..00000000 --- a/eth_account/deterministic.py +++ /dev/null @@ -1,210 +0,0 @@ -#from bitcoin.main import * -from utils import * -import hmac -import hashlib -#from binascii import hexlify - - -''' -This library was initially included from pybitcointools -https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py -''' - -# Electrum wallets - -def electrum_stretch(seed): - return slowsha(seed) - -# Accepts seed or stretched seed, returns master public key - - -def electrum_mpk(seed): - if len(seed) == 32: - seed = electrum_stretch(seed) - return privkey_to_pubkey(seed)[2:] - -# Accepts (seed or stretched seed), index and secondary index -# (conventionally 0 for ordinary addresses, 1 for change) , returns privkey - - -def electrum_privkey(seed, n, for_change=0): - if len(seed) == 32: - seed = electrum_stretch(seed) - mpk = electrum_mpk(seed) - offset = dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+binascii.unhexlify(mpk)) - return add_privkeys(seed, offset) - -# Accepts (seed or stretched seed or master pubkey), index and secondary index -# (conventionally 0 for ordinary addresses, 1 for change) , returns pubkey - - -def electrum_pubkey(masterkey, n, for_change=0): - if len(masterkey) == 32: - mpk = electrum_mpk(electrum_stretch(masterkey)) - elif len(masterkey) == 64: - mpk = electrum_mpk(masterkey) - else: - mpk = masterkey - bin_mpk = encode_pubkey(mpk, 'bin_electrum') - offset = bin_dbl_sha256(from_int_representation_to_bytes(n)+b':'+from_int_representation_to_bytes(for_change)+b':'+bin_mpk) - return add_pubkeys('04'+mpk, privtopub(offset)) - -# seed/stretched seed/pubkey -> address (convenience method) - - -def electrum_address(masterkey, n, for_change=0, version=0): - return pubkey_to_address(electrum_pubkey(masterkey, n, for_change), version) - -# Given a master public key, a private key from that wallet and its index, -# cracks the secret exponent which can be used to generate all other private -# keys in the wallet - - -def crack_electrum_wallet(mpk, pk, n, for_change=0): - bin_mpk = encode_pubkey(mpk, 'bin_electrum') - offset = dbl_sha256(str(n)+':'+str(for_change)+':'+bin_mpk) - return subtract_privkeys(pk, offset) - -# Below code ASSUMES binary inputs and compressed pubkeys -MAINNET_PRIVATE = b'\x04\x88\xAD\xE4' -MAINNET_PUBLIC = b'\x04\x88\xB2\x1E' -TESTNET_PRIVATE = b'\x04\x35\x83\x94' -TESTNET_PUBLIC = b'\x04\x35\x87\xCF' -PRIVATE = [MAINNET_PRIVATE, TESTNET_PRIVATE] -PUBLIC = [MAINNET_PUBLIC, TESTNET_PUBLIC] - -# BIP32 child key derivation - - -def raw_bip32_ckd(rawtuple, i): - vbytes, depth, fingerprint, oldi, chaincode, key = rawtuple - i = int(i) - - if vbytes in PRIVATE: - priv = key - pub = privtopub(key) - else: - pub = key - - if i >= 2**31: - if vbytes in PUBLIC: - raise Exception("Can't do private derivation on public key!") - I = hmac.new(chaincode, b'\x00'+priv[:32]+encode(i, 256, 4), hashlib.sha512).digest() - else: - I = hmac.new(chaincode, pub+encode(i, 256, 4), hashlib.sha512).digest() - - if vbytes in PRIVATE: - newkey = add_privkeys(I[:32]+B'\x01', priv) - fingerprint = bin_hash160(privtopub(key))[:4] - if vbytes in PUBLIC: - newkey = add_pubkeys(compress(privtopub(I[:32])), key) - fingerprint = bin_hash160(key)[:4] - - return (vbytes, depth + 1, fingerprint, i, I[32:], newkey) - - -def bip32_serialize(rawtuple): - vbytes, depth, fingerprint, i, chaincode, key = rawtuple - i = encode(i, 256, 4) - chaincode = encode(hash_to_int(chaincode), 256, 32) - keydata = b'\x00'+key[:-1] if vbytes in PRIVATE else key - bindata = vbytes + from_int_to_byte(depth % 256) + fingerprint + i + chaincode + keydata - return changebase(bindata+bin_dbl_sha256(bindata)[:4], 256, 58) - - -def bip32_deserialize(data): - dbin = changebase(data, 58, 256) - if bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: - raise Exception("Invalid checksum") - vbytes = dbin[0:4] - depth = from_byte_to_int(dbin[4]) - fingerprint = dbin[5:9] - i = decode(dbin[9:13], 256) - chaincode = dbin[13:45] - key = dbin[46:78]+b'\x01' if vbytes in PRIVATE else dbin[45:78] - return (vbytes, depth, fingerprint, i, chaincode, key) - - -def raw_bip32_privtopub(rawtuple): - vbytes, depth, fingerprint, i, chaincode, key = rawtuple - newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC - return (newvbytes, depth, fingerprint, i, chaincode, privtopub(key)) - - -def bip32_privtopub(data): - return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) - - -def bip32_ckd(data, i): - return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) - - -def bip32_master_key(seed, vbytes=MAINNET_PRIVATE): - I = hmac.new( - from_string_to_bytes("Bitcoin seed"), - from_string_to_bytes(seed), - hashlib.sha512 - ).digest() - return bip32_serialize((vbytes, 0, b'\x00'*4, 0, I[32:], I[:32]+b'\x01')) - - -def bip32_bin_extract_key(data): - return bip32_deserialize(data)[-1] - - -def bip32_extract_key(data): - return safe_hexlify(bip32_deserialize(data)[-1]) - -# Exploits the same vulnerability as above in Electrum wallets -# Takes a BIP32 pubkey and one of the child privkeys of its corresponding -# privkey and returns the BIP32 privkey associated with that pubkey - - -def raw_crack_bip32_privkey(parent_pub, priv): - vbytes, depth, fingerprint, i, chaincode, key = priv - pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub - i = int(i) - - if i >= 2**31: - raise Exception("Can't crack private derivation!") - - I = hmac.new(pchaincode, pkey+encode(i, 256, 4), hashlib.sha512).digest() - - pprivkey = subtract_privkeys(key, I[:32]+b'\x01') - - newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE - return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) - - -def crack_bip32_privkey(parent_pub, priv): - dsppub = bip32_deserialize(parent_pub) - dspriv = bip32_deserialize(priv) - return bip32_serialize(raw_crack_bip32_privkey(dsppub, dspriv)) - - -def coinvault_pub_to_bip32(*args): - if len(args) == 1: - args = args[0].split(' ') - vals = map(int, args[34:]) - I1 = ''.join(map(chr, vals[:33])) - I2 = ''.join(map(chr, vals[35:67])) - return bip32_serialize((MAINNET_PUBLIC, 0, b'\x00'*4, 0, I2, I1)) - - -def coinvault_priv_to_bip32(*args): - if len(args) == 1: - args = args[0].split(' ') - vals = map(int, args[34:]) - I2 = ''.join(map(chr, vals[35:67])) - I3 = ''.join(map(chr, vals[72:104])) - return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00'*4, 0, I2, I3+b'\x01')) - - -def bip32_descend(*args): - if len(args) == 2 and isinstance(args[1], list): - key, path = args - else: - key, path = args[0], map(int, args[1:]) - for p in path: - key = bip32_ckd(key, p) -return bip32_extract_key(key) diff --git a/eth_account/utils.py b/eth_account/utils.py deleted file mode 100644 index cdbaab2a..00000000 --- a/eth_account/utils.py +++ /dev/null @@ -1,686 +0,0 @@ -from pyspecials import * -import binascii -import hashlib -import re -import base64 -import time -import random -import hmac - -''' -This library is composite from main.py in pybitcointools -https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/deterministic.py -''' - - -# Elliptic curve parameters (secp256k1) - -P = 2**256 - 2**32 - 977 -N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 -A = 0 -B = 7 -Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240 -Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424 -G = (Gx, Gy) - - -def change_curve(p, n, a, b, gx, gy): - global P, N, A, B, Gx, Gy, G - P, N, A, B, Gx, Gy = p, n, a, b, gx, gy - G = (Gx, Gy) - - -def getG(): - return G - -# Extended Euclidean Algorithm - - -def inv(a, n): - if a == 0: - return 0 - lm, hm = 1, 0 - low, high = a % n, n - while low > 1: - r = high // low - nm, new = hm - lm * r, high - low * r - lm, low, hm, high = nm, new, lm, low - return lm % n - -# JSON access (for pybtctool convenience) - - -def access(obj, prop): - if isinstance(obj, dict): - if prop in obj: - return obj[prop] - elif '.' in prop: - return obj[float(prop)] - else: - return obj[int(prop)] - else: - return obj[int(prop)] - - -def multiaccess(obj, prop): - return [access(o, prop) for o in obj] - - -def slice(obj, start=0, end=2**200): - return obj[int(start):int(end)] - - -def count(obj): - return len(obj) - - -_sum = sum - - -def sum(obj): - return _sum(obj) - - -def isinf(p): - return p[0] == 0 and p[1] == 0 - - -def to_jacobian(p): - o = (p[0], p[1], 1) - return o - - -def jacobian_double(p): - if not p[1]: - return (0, 0, 0) - ysq = (p[1] ** 2) % P - S = (4 * p[0] * ysq) % P - M = (3 * p[0] ** 2 + A * p[2] ** 4) % P - nx = (M**2 - 2 * S) % P - ny = (M * (S - nx) - 8 * ysq ** 2) % P - nz = (2 * p[1] * p[2]) % P - return (nx, ny, nz) - - -def jacobian_add(p, q): - if not p[1]: - return q - if not q[1]: - return p - U1 = (p[0] * q[2] ** 2) % P - U2 = (q[0] * p[2] ** 2) % P - S1 = (p[1] * q[2] ** 3) % P - S2 = (q[1] * p[2] ** 3) % P - if U1 == U2: - if S1 != S2: - return (0, 0, 1) - return jacobian_double(p) - H = U2 - U1 - R = S2 - S1 - H2 = (H * H) % P - H3 = (H * H2) % P - U1H2 = (U1 * H2) % P - nx = (R ** 2 - H3 - 2 * U1H2) % P - ny = (R * (U1H2 - nx) - S1 * H3) % P - nz = (H * p[2] * q[2]) % P - return (nx, ny, nz) - - -def from_jacobian(p): - z = inv(p[2], P) - return ((p[0] * z**2) % P, (p[1] * z**3) % P) - - -def jacobian_multiply(a, n): - if a[1] == 0 or n == 0: - return (0, 0, 1) - - if n == 1: - return a - - if n < 0 or n >= N: - return jacobian_multiply(a, n % N) - - if (n % 2) == 0: - return jacobian_double(jacobian_multiply(a, n // 2)) - - if (n % 2) == 1: - return jacobian_add(jacobian_double(jacobian_multiply(a, n // 2)), a) - - -def fast_multiply(a, n): - return from_jacobian(jacobian_multiply(to_jacobian(a), n)) - - -def fast_add(a, b): - return from_jacobian(jacobian_add(to_jacobian(a), to_jacobian(b))) - -# Functions for handling pubkey and privkey formats - - -def get_pubkey_format(pub): - if is_python2: - two = '\x02' - three = '\x03' - four = '\x04' - else: - two = 2 - three = 3 - four = 4 - - if isinstance(pub, (tuple, list)): - return 'decimal' - elif len(pub) == 65 and pub[0] == four: - return 'bin' - elif len(pub) == 130 and pub[0:2] == '04': - return 'hex' - elif len(pub) == 33 and pub[0] in [two, three]: - return 'bin_compressed' - elif len(pub) == 66 and pub[0:2] in ['02', '03']: - return 'hex_compressed' - elif len(pub) == 64: - return 'bin_electrum' - elif len(pub) == 128: - return 'hex_electrum' - else: - raise Exception("Pubkey not in recognized format") - - -def encode_pubkey(pub, formt): - if not isinstance(pub, (tuple, list)): - pub = decode_pubkey(pub) - - if formt == 'decimal': - return pub - elif formt == 'bin': - return b'\x04' + encode(pub[0], 256, 32) + encode(pub[1], 256, 32) - elif formt == 'bin_compressed': - return from_int_to_byte(2 + (pub[1] % 2)) + encode(pub[0], 256, 32) - elif formt == 'hex': - return '04' + encode(pub[0], 16, 64) + encode(pub[1], 16, 64) - elif formt == 'hex_compressed': - return '0' + str(2 + (pub[1] % 2)) + encode(pub[0], 16, 64) - elif formt == 'bin_electrum': - return encode(pub[0], 256, 32) + encode(pub[1], 256, 32) - elif formt == 'hex_electrum': - return encode(pub[0], 16, 64) + encode(pub[1], 16, 64) - else: - raise Exception("Invalid format!") - - -def decode_pubkey(pub, formt=None): - if not formt: - formt = get_pubkey_format(pub) - - if formt == 'decimal': - return pub - elif formt == 'bin': - return (decode(pub[1:33], 256), decode(pub[33:65], 256)) - elif formt == 'bin_compressed': - x = decode(pub[1:33], 256) - beta = pow(int(x * x * x + A * x + B), int((P + 1) // 4), int(P)) - y = (P - beta) if ((beta + from_byte_to_int(pub[0])) % 2) else beta - return (x, y) - elif formt == 'hex': - return (decode(pub[2:66], 16), decode(pub[66:130], 16)) - elif formt == 'hex_compressed': - return decode_pubkey(safe_from_hex(pub), 'bin_compressed') - elif formt == 'bin_electrum': - return (decode(pub[:32], 256), decode(pub[32:64], 256)) - elif formt == 'hex_electrum': - return (decode(pub[:64], 16), decode(pub[64:128], 16)) - else: - raise Exception("Invalid format!") - - -def get_privkey_format(priv): - if isinstance(priv, int_types): - return 'decimal' - elif len(priv) == 32: - return 'bin' - elif len(priv) == 33: - return 'bin_compressed' - elif len(priv) == 64: - return 'hex' - elif len(priv) == 66: - return 'hex_compressed' - else: - bin_p = b58check_to_bin(priv) - - if len(bin_p) == 32: - return 'wif' - elif len(bin_p) == 33: - return 'wif_compressed' - else: - raise Exception("WIF does not represent privkey") - - -def encode_privkey(priv, formt, vbyte=0): - if not isinstance(priv, int_types): - return encode_privkey(decode_privkey(priv), formt, vbyte) - - if formt == 'decimal': - return priv - elif formt == 'bin': - return encode(priv, 256, 32) - elif formt == 'bin_compressed': - return encode(priv, 256, 32)+b'\x01' - elif formt == 'hex': - return encode(priv, 16, 64) - elif formt == 'hex_compressed': - return encode(priv, 16, 64)+'01' - elif formt == 'wif': - return bin_to_b58check(encode(priv, 256, 32), 128+int(vbyte)) - elif formt == 'wif_compressed': - return bin_to_b58check(encode(priv, 256, 32)+b'\x01', 128+int(vbyte)) - else: - raise Exception("Invalid format!") - -def decode_privkey(priv,formt=None): - if not formt: - formt = get_privkey_format(priv) - - if formt == 'decimal': - return priv - elif formt == 'bin': - return decode(priv, 256) - elif formt == 'bin_compressed': - return decode(priv[:32], 256) - elif formt == 'hex': - return decode(priv, 16) - elif formt == 'hex_compressed': - return decode(priv[:64], 16) - elif formt == 'wif': - return decode(b58check_to_bin(priv),256) - elif formt == 'wif_compressed': - return decode(b58check_to_bin(priv)[:32],256) - else: - raise Exception("WIF does not represent privkey") - - -def add_pubkeys(p1, p2): - f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) - return encode_pubkey(fast_add(decode_pubkey(p1, f1), decode_pubkey(p2, f2)), f1) - - -def add_privkeys(p1, p2): - f1, f2 = get_privkey_format(p1), get_privkey_format(p2) - return encode_privkey((decode_privkey(p1, f1) + decode_privkey(p2, f2)) % N, f1) - - -def mul_privkeys(p1, p2): - f1, f2 = get_privkey_format(p1), get_privkey_format(p2) - return encode_privkey((decode_privkey(p1, f1) * decode_privkey(p2, f2)) % N, f1) - - -def multiply(pubkey, privkey): - f1, f2 = get_pubkey_format(pubkey), get_privkey_format(privkey) - pubkey, privkey = decode_pubkey(pubkey, f1), decode_privkey(privkey, f2) - - # http://safecurves.cr.yp.to/twist.html - if not isinf(pubkey) and (pubkey[0] ** 3 + B - pubkey[1] * pubkey[1]) % P != 0: - raise Exception("Point not on curve") - - return encode_pubkey(fast_multiply(pubkey, privkey), f1) - - -def divide(pubkey, privkey): - factor = inv(decode_privkey(privkey), N) - return multiply(pubkey, factor) - - -def compress(pubkey): - f = get_pubkey_format(pubkey) - - if 'compressed' in f: - return pubkey - elif f == 'bin': - return encode_pubkey(decode_pubkey(pubkey, f), 'bin_compressed') - elif f == 'hex' or f == 'decimal': - return encode_pubkey(decode_pubkey(pubkey, f), 'hex_compressed') - - -def decompress(pubkey): - f = get_pubkey_format(pubkey) - - if 'compressed' not in f: - return pubkey - elif f == 'bin_compressed': - return encode_pubkey(decode_pubkey(pubkey, f), 'bin') - elif f == 'hex_compressed' or f == 'decimal': - return encode_pubkey(decode_pubkey(pubkey, f), 'hex') - - -def privkey_to_pubkey(privkey): - f = get_privkey_format(privkey) - privkey = decode_privkey(privkey, f) - if privkey >= N: - raise Exception("Invalid privkey") - if f in ['bin', 'bin_compressed', 'hex', 'hex_compressed', 'decimal']: - return encode_pubkey(fast_multiply(G, privkey), f) - else: - return encode_pubkey(fast_multiply(G, privkey), f.replace('wif', 'hex')) - - -privtopub = privkey_to_pubkey - - -def privkey_to_address(priv, magicbyte=0): - return pubkey_to_address(privkey_to_pubkey(priv), magicbyte) - - -privtoaddr = privkey_to_address - - -def neg_pubkey(pubkey): - f = get_pubkey_format(pubkey) - pubkey = decode_pubkey(pubkey, f) - return encode_pubkey((pubkey[0], (P - pubkey[1]) % P), f) - - -def neg_privkey(privkey): - f = get_privkey_format(privkey) - privkey = decode_privkey(privkey, f) - return encode_privkey((N - privkey) % N, f) - - -def subtract_pubkeys(p1, p2): - f1, f2 = get_pubkey_format(p1), get_pubkey_format(p2) - k2 = decode_pubkey(p2, f2) - return encode_pubkey(fast_add(decode_pubkey(p1, f1), (k2[0], (P - k2[1]) % P)), f1) - - -def subtract_privkeys(p1, p2): - f1, f2 = get_privkey_format(p1), get_privkey_format(p2) - k2 = decode_privkey(p2, f2) - return encode_privkey((decode_privkey(p1, f1) - k2) % N, f1) - - -# Hashes - - -def bin_hash160(string): - intermed = hashlib.sha256(string).digest() - digest = '' - - try: - digest = hashlib.new('ripemd160', intermed).digest() - except Exception: - digest = RIPEMD160(intermed).digest() - - return digest - - -def hash160(string): - return safe_hexlify(bin_hash160(string)) - - -def bin_sha256(string): - binary_data = string if isinstance(string, bytes) else bytes(string, 'utf-8') - return hashlib.sha256(binary_data).digest() - - -def sha256(string): - return bytes_to_hex_string(bin_sha256(string)) - - -def bin_ripemd160(string): - try: - digest = hashlib.new('ripemd160', string).digest() - except Exception: - digest = RIPEMD160(string).digest() - return digest - - -def ripemd160(string): - return safe_hexlify(bin_ripemd160(string)) - - -def bin_dbl_sha256(s): - bytes_to_hash = from_string_to_bytes(s) - return hashlib.sha256(hashlib.sha256(bytes_to_hash).digest()).digest() - - -def dbl_sha256(string): - return safe_hexlify(bin_dbl_sha256(string)) - - -def bin_slowsha(string): - string = from_string_to_bytes(string) - orig_input = string - for i in range(100000): - string = hashlib.sha256(string + orig_input).digest() - return string - - -def slowsha(string): - return safe_hexlify(bin_slowsha(string)) - - -def hash_to_int(x): - if len(x) in [40, 64]: - return decode(x, 16) - return decode(x, 256) - - -def num_to_var_int(x): - x = int(x) - - if x < 253: - return from_int_to_byte(x) - elif x < 65536: - return from_int_to_byte(253)+encode(x, 256, 2)[::-1] - elif x < 4294967296: - return from_int_to_byte(254) + encode(x, 256, 4)[::-1] - else: - return from_int_to_byte(255) + encode(x, 256, 8)[::-1] - - -# WTF, Electrum? -def electrum_sig_hash(message): - padded = b"\x18Bitcoin Signed Message:\n" + num_to_var_int(len(message))\ - + from_string_to_bytes(message) - return bin_dbl_sha256(padded) - - -def random_key(): - # Gotta be secure after that java.SecureRandom fiasco... - entropy = random_string(32) \ - + str(random.randrange(2**256)) \ - + str(int(time.time() * 1000000)) - return sha256(entropy) - - -def random_electrum_seed(): - # entropy = os.urandom(32) \ # fails in Python 3, hence copied from random_key() - entropy = random_string(32) \ - + str(random.randrange(2**256)) \ - + str(int(time.time() * 1000000)) - return sha256(entropy)[:32] - - -# Encodings - - -def b58check_to_bin(inp): - leadingzbytes = len(re.match('^1*', inp).group(0)) - data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) - assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] - return data[1:-4] - - -def get_version_byte(inp): - leadingzbytes = len(re.match('^1*', inp).group(0)) - data = b'\x00' * leadingzbytes + changebase(inp, 58, 256) - assert bin_dbl_sha256(data[:-4])[:4] == data[-4:] - return ord(data[0]) - - -def hex_to_b58check(inp, magicbyte=0): - return bin_to_b58check(binascii.unhexlify(inp), magicbyte) - - -def b58check_to_hex(inp): - return safe_hexlify(b58check_to_bin(inp)) - - -def pubkey_to_address(pubkey, magicbyte=0): - if isinstance(pubkey, (list, tuple)): - pubkey = encode_pubkey(pubkey, 'bin') - if len(pubkey) in [66, 130]: - return bin_to_b58check( - bin_hash160(binascii.unhexlify(pubkey)), magicbyte) - return bin_to_b58check(bin_hash160(pubkey), magicbyte) - - -pubtoaddr = pubkey_to_address - - -def is_privkey(priv): - try: - get_privkey_format(priv) - return True - except Exception: - return False - - -def is_pubkey(pubkey): - try: - get_pubkey_format(pubkey) - return True - except Exception: - return False - - -def is_address(addr): - ADDR_RE = re.compile("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$") - return bool(ADDR_RE.match(addr)) - - -# EDCSA - - -def encode_sig(v, r, s): - vb, rb, sb = from_int_to_byte(v), encode(r, 256), encode(s, 256) - result = base64.b64encode(vb + b'\x00' * (32 - len(rb)) + rb - + b'\x00' * (32 - len(sb)) + sb) - return result if is_python2 else str(result, 'utf-8') - - -def decode_sig(sig): - bytez = base64.b64decode(sig) - return from_byte_to_int(bytez[0]), decode(bytez[1:33], 256), decode(bytez[33:], 256) - -# https://tools.ietf.org/html/rfc6979#section-3.2 - - -''' -def deterministic_generate_k(msghash, priv): - v = b'\x01' * 32 - k = b'\x00' * 32 - priv = encode_privkey(priv, 'bin') - msghash = encode(hash_to_int(msghash), 256, 32) - k = hmac.new(k, v+b'\x00' + priv + msghash, hashlib.sha256).digest() - v = hmac.new(k, v, hashlib.sha256).digest() - k = hmac.new(k, v+b'\x01' + priv + msghash, hashlib.sha256).digest() - v = hmac.new(k, v, hashlib.sha256).digest() - return decode(hmac.new(k, v, hashlib.sha256).digest(), 256) - - -def ecdsa_raw_sign(msghash, priv): - - z = hash_to_int(msghash) - k = deterministic_generate_k(msghash, priv) - - r, y = fast_multiply(G, k) - s = inv(k, N) * (z + r*decode_privkey(priv)) % N - - v, r, s = 27 + ((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s - - if 'compressed' in get_privkey_format(priv): - v += 4 - - return v, r, s - - -def ecdsa_sign(msg, priv): - v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) - sig = encode_sig(v, r, s) - - assert ecdsa_verify(msg, sig, privtopub(priv)), \ - "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) - - return sig - - -def ecdsa_raw_verify(msghash, vrs, pub): - v, r, s = vrs - if not (27 <= v <= 34): - return False - - w = inv(s, N) - z = hash_to_int(msghash) - - u1, u2 = z * w % N, r * w % N - x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) - return bool(r == x and (r % N) and (s % N)) - - -# For BitcoinCore, (msg = addr or msg = "") be default -def ecdsa_verify_addr(msg, sig, addr): - assert is_address(addr) - Q = ecdsa_recover(msg, sig) - magic = get_version_byte(addr) - return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) - - -def ecdsa_verify(msg, sig, pub): - if is_address(pub): - return ecdsa_verify_addr(msg, sig, pub) - return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) - - -def ecdsa_raw_recover(msghash, vrs): - v, r, s = vrs - if not (27 <= v <= 34): - raise ValueError("%d must in range 27-31" % v) - x = r - xcubedaxb = (x*x*x+A*x+B) % P - beta = pow(xcubedaxb, (P+1)//4, P) - y = beta if v % 2 ^ beta % 2 else (P - beta) - # If xcubedaxb is not a quadratic residue, then r cannot be the x coord - # for a point on the curve, and so the sig is invalid - if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): - return False - z = hash_to_int(msghash) - Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) - XY = jacobian_multiply((x, y, 1), s) - Qr = jacobian_add(Gz, XY) - Q = jacobian_multiply(Qr, inv(r, N)) - Q = from_jacobian(Q) - - # if ecdsa_raw_verify(msghash, vrs, Q): - return Q - # return False - - -def ecdsa_recover(msg, sig): - v,r,s = decode_sig(sig) - Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) - return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') - -''' - - -# add/subtract -def add(p1, p2): - if is_privkey(p1): - return add_privkeys(p1, p2) - else: - return add_pubkeys(p1, p2) - -def subtract(p1, p2): - if is_privkey(p1): - return subtract_privkeys(p1, p2) - else: - return subtract_pubkeys(p1, p2) From b944a2b2d97418de32c9a6fca4a299578b25b28a Mon Sep 17 00:00:00 2001 From: sea212 Date: Sat, 1 Sep 2018 22:03:33 +0200 Subject: [PATCH 12/27] Implemented create and init function for HD Accounts, added wordlist path to setup.py, debugged mnemonic.py to work with python3, adapted function commenting style for the most functions --- eth_account/hdaccount.py | 165 +++++++++++++----- eth_account/hdaccount/__init__.py | 0 eth_account/hdaccount/deterministic.py | 77 ++++---- eth_account/hdaccount/mnemonic.py | 30 ++-- eth_account/hdaccount/utils.py | 4 +- .../{ => wordlist}/bip39_english.txt | 0 setup.py | 1 + 7 files changed, 181 insertions(+), 96 deletions(-) create mode 100644 eth_account/hdaccount/__init__.py rename eth_account/hdaccount/{ => wordlist}/bip39_english.txt (100%) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index f0411a0c..3bfb0a3a 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -1,13 +1,12 @@ -''' -from bitcoin import ( - deterministic, - mnemonic, +from os import ( + urandom, ) -from hexbytes import ( - HexBytes, +from os.path import ( + join as ojoin, + dirname, + realpath, ) -''' from eth_account.datastructures import ( AttributeDict, @@ -15,6 +14,17 @@ from eth_account.signers.base import ( BaseAccount, ) +from eth_account.hdaccount.deterministic import ( + bip32_master_key, +) +from eth_account.hdaccount.mnemonic import ( + entropy_to_words, + mnemonic_to_seed, +) + + +default_wordlist = ojoin(dirname(realpath(__file__)), + 'hdaccount/wordlist/bip39_english.txt') class HDAccount(BaseAccount): @@ -22,41 +32,29 @@ class HDAccount(BaseAccount): This class manages BIP32 HD-Accounts for Ethereum ''' - def __init__(self, privkey: str = "", pubkey: tuple = ("", ""), - chaindata: str = ""): + def __init__(self, encoded_key=""): ''' The object is getting initialized here. You can pass no parameters, in which case the object will be created containing no information. - You can create an account later by calling initAccount(...) in this case. - You can pass only a private key and chaindata, in which case the - public key will be automatically calculated. You can derive keypairs. - You can pass only a public key and chaindata, in which case only - public keys can be derived. - Pass the arguments as hex strings. + You can create an account later by calling createAccount(...) in this case. + You can also initialize an account with a mnemonic code by using initAccount() + You can pass only an encoded key, in which case the class is initialized + by using the information contained within the encoded key. ''' - # bip32 magic number for derivation of hardened childs - self.__hardened = 0x80000000 + # Contains derivation path. The class will automatically fill this + self._path = [] - if (privkey, pubkey, chaindata) == ("", ("", ""), ""): - # create uninitialized account - self._privkey = self._chaindata = b"" - self._pubkey = (b"", b"") + # Contains either encoded private key (hardened derivation possible) + # or public key + self.__key = encoded_key - elif (privkey, chaindata) != ("", ""): - # initialize account with a private key and a chaincode - # TODO - # TODO derive public key - pass + # Initiates the account generator + self.__accgen = self._accountGenerator() + self.__accgen.send(None) - elif (pubkey, chaindata) != ("", ""): - # initialize account only with a public key and a chaincode - # TODO - pass - - # Initiate account generator - self._accgen = self._accountGenerator(0) - next(self._accgen) + # Magic number for hardened key derivation (see BIP32) + self.__const_hardened = 0x80000000 def _accountGenerator(self, curindex: int = 0): ''' @@ -65,8 +63,6 @@ def _accountGenerator(self, curindex: int = 0): You can either send None to this generator, in which case it just increments the index and returns you the derived child object for that index or you can send an index. - If no private key is specified but a public key is specified, - the derived child will be derived using only the public key ''' # This variable will contain the object of the last derived child @@ -94,22 +90,83 @@ def deriveChild(self, cid: int = None) -> "HDAccount": This function generates a new account by using the __accountGenerator function. You can specify an index. Not specifying an index leads to the usage of the old index + 1 + :param int cid: (OPTIONAL) contains child index ''' + # catch invalid argument type error, otherwise it will break our generator + if cid is not None or not isinstance(cid, int): + raise TypeError("Excepted integer as index") + # TODO implement this gateway function for __accountGenerator(...) # check if this object is initialized return self.__accgen.send(cid) - def createAccount(self, password: str = "", ent_bits: int = 256) -> str: + def derivePath(self, path) -> "HDAccount": + ''' + This function receives a derivation path and returns + an HDAccount object for the given path + :param path : contains the derivation path, example: "m/12H/1" or [0x8000000C, 1] + :type path : str or list + :returns : HDAccount for the desired path + :rtype : HDAccount + ''' + pass + + def createAccount(self, password: str = "", ent_bytes: int = 32, wordlist: str = None) -> str: ''' This function initiates an account from scratch After completing the initiation it returns the mnemonic code + :param list password : additional password for mnemonic + :param list ent_bytes : amount of entropy bytes for generations of the + mnemonic code has to be in [16, 20, 24, 28, 32] + :param str wordlist : path to wordlist including name of wordlist, + defaults to english + :returns : mnemonic code + :rtype str ''' - # TODO random from ent pool -> add checksum -> create mnemonic -> - # PBKDF2-HMAC-SHA512(mnemonic, pw) -> HMAC-SHA512(u"Bitcoin Seed", rootseed) - # -> 32 Byte priv key | 32 Byte chaindata - pass + # generate mnemonic (uses english word book by default) + # the function will perfom the value check for entropy bytes + + if wordlist is None: + wordlist = default_wordlist + + with open(wordlist) as f_wl: + wordlist_content = f_wl.readlines() + + mnemonic = entropy_to_words(urandom(ent_bytes), wordlist_content) + self.initAccount(mnemonic, password) + return " ".join(mnemonic) + + def initAccount(self, mnemonic, password: str = ""): + ''' + This function initiates an account by using a mnemonic code + and an optional password + :param mnemonic : the mnemonic code to derive the master keys from + :type mnemonic : str or list + :param str password : the password required to successfully derive + the master keys from the mnemonic code + ''' + + if isinstance(mnemonic, str): + seed = mnemonic_to_seed(mnemonic.encode("utf-8"), password.encode("utf-8")) + elif isinstance(mnemonic, list): + seed = mnemonic_to_seed(" ".join(mnemonic).encode("utf-8"), password.encode("utf-8")) + else: + raise TypeError("Mnemonic has to be formated as a list or a string") + + # create seed from mnemonic and derive key (in bip32 serialization format) + self.__key = bip32_master_key(seed) + + @property + def key(self): + ''' + Returns the bip32 serialized key of this instance of HDAccount + :returns: Bip32 serilaized key + :rtype : str + ''' + + return self.__key @property def address(self) -> str: @@ -125,7 +182,7 @@ def signHash(self, message_hash: bytes) -> AttributeDict: ''' Sign the hash of a message, as in :meth:`~eth_account.account.Account.signHash` but without specifying the private key. - :var bytes message_hash: 32 byte hash of the message to sign + :param bytes message_hash: 32 byte hash of the message to sign ''' pass @@ -133,7 +190,7 @@ def signTransaction(self, transaction_dict: dict) -> AttributeDict: ''' Sign a transaction, as in :meth:`~eth_account.account.Account.signTransaction` but without specifying the private key. - :var dict transaction_dict: transaction with all fields specified + :param dict transaction_dict: transaction with all fields specified ''' pass @@ -147,3 +204,25 @@ def __eq__(self, other): def __hash__(self): return hash((type(self), self.address)) + + +# Example on how to use this class +if __name__ == "__main__": + # Create empty HDAccount object + hdacc = HDAccount() + + # Create account + print("Creating new Account") + pw = "supersecret" + mnemonic = hdacc.createAccount(pw) + print("Mnemonic code: {}\nPassword: {}\nKey: {}\n".format(mnemonic, pw, hdacc.key)) + + # Init account + print("Initializing new Account with the same mnemonic and password") + hdacc2 = HDAccount() + hdacc2.initAccount(mnemonic, pw) + print("Key: ", hdacc2.key) + + # Check if the creation of the account and the initialization share the same result + assert(hdacc.key == hdacc2.key) + print("Success!") diff --git a/eth_account/hdaccount/__init__.py b/eth_account/hdaccount/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py index b2b5b0c2..b7d0fa17 100644 --- a/eth_account/hdaccount/deterministic.py +++ b/eth_account/hdaccount/deterministic.py @@ -1,8 +1,13 @@ import hashlib import hmac -import pyspecials -import utils +from eth_account.hdaccount import ( + pyspecials, +) + +from eth_account.hdaccount import ( + utils, +) # from binascii import unhexlify # Electrum wallets @@ -116,6 +121,28 @@ def raw_bip32_ckd(rawtuple, i): return (vbytes, depth + 1, fingerprint, i, hmac_res[32:], newkey) +def raw_bip32_privtopub(rawtuple): + vbytes, depth, fingerprint, i, chaincode, key = rawtuple + newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC + return (newvbytes, depth, fingerprint, i, chaincode, utils.privtopub(key)) + + +def raw_crack_bip32_privkey(parent_pub, priv): + vbytes, depth, fingerprint, i, chaincode, key = priv + pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub + i = int(i) + + if i >= 2**31: + raise Exception("Can't crack private derivation!") + + hmac_res = hmac.new(pchaincode, pkey + pyspecials.encode(i, 256, 4), hashlib.sha512).digest() + + pprivkey = utils.subtract_privkeys(key, hmac_res[:32] + b'\x01') + + newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE + return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) + + def bip32_serialize(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple i = pyspecials.encode(i, 256, 4) @@ -128,8 +155,10 @@ def bip32_serialize(rawtuple): def bip32_deserialize(data): dbin = pyspecials.changebase(data, 58, 256) + if pyspecials.bin_dbl_sha256(dbin[:-4])[:4] != dbin[-4:]: raise Exception("Invalid checksum") + vbytes = dbin[0:4] depth = pyspecials.from_byte_to_int(dbin[4]) fingerprint = dbin[5:9] @@ -139,12 +168,6 @@ def bip32_deserialize(data): return (vbytes, depth, fingerprint, i, chaincode, key) -def raw_bip32_privtopub(rawtuple): - vbytes, depth, fingerprint, i, chaincode, key = rawtuple - newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC - return (newvbytes, depth, fingerprint, i, chaincode, utils.privtopub(key)) - - def bip32_privtopub(data): return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) @@ -167,25 +190,21 @@ def bip32_bin_extract_key(data): def bip32_extract_key(data): return pyspecials.safe_hexlify(bip32_deserialize(data)[-1]) -# Exploits the same vulnerability as above in Electrum wallets -# Takes a BIP32 pubkey and one of the child privkeys of its corresponding -# privkey and returns the BIP32 privkey associated with that pubkey - -def raw_crack_bip32_privkey(parent_pub, priv): - vbytes, depth, fingerprint, i, chaincode, key = priv - pvbytes, pdepth, pfingerprint, pi, pchaincode, pkey = parent_pub - i = int(i) - - if i >= 2**31: - raise Exception("Can't crack private derivation!") +def bip32_descend(*args): + if len(args) == 2 and isinstance(args[1], list): + key, path = args + else: + key, path = args[0], map(int, args[1:]) - hmac_res = hmac.new(pchaincode, pkey + pyspecials.encode(i, 256, 4), hashlib.sha512).digest() + for p in path: + key = bip32_ckd(key, p) - pprivkey = utils.subtract_privkeys(key, hmac_res[:32] + b'\x01') + return bip32_extract_key(key) - newvbytes = MAINNET_PRIVATE if vbytes == MAINNET_PUBLIC else TESTNET_PRIVATE - return (newvbytes, pdepth, pfingerprint, pi, pchaincode, pprivkey) +# Exploits the same vulnerability as above in Electrum wallets +# Takes a BIP32 pubkey and one of the child privkeys of its corresponding +# privkey and returns the BIP32 privkey associated with that pubkey def crack_bip32_privkey(parent_pub, priv): @@ -210,15 +229,3 @@ def coinvault_priv_to_bip32(*args): I2 = ''.join(map(chr, vals[35:67])) I3 = ''.join(map(chr, vals[72:104])) return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00' * 4, 0, I2, I3 + b'\x01')) - - -def bip32_descend(*args): - if len(args) == 2 and isinstance(args[1], list): - key, path = args - else: - key, path = args[0], map(int, args[1:]) - - for p in path: - key = bip32_ckd(key, p) - - return bip32_extract_key(key) diff --git a/eth_account/hdaccount/mnemonic.py b/eth_account/hdaccount/mnemonic.py index 2b5a67d1..0c00d94f 100644 --- a/eth_account/hdaccount/mnemonic.py +++ b/eth_account/hdaccount/mnemonic.py @@ -3,17 +3,14 @@ bisect_left, ) import hashlib -import os.path import random + ''' This library was initially included from pybitcointools https://github.com/vbuterin/pybitcointools/blob/aeb0a2bbb8bbfe421432d776c649650eaeb882a5/bitcoin/mnemonic.py ''' -wordlist_english = open(os.path.join(os.path.dirname( - os.path.realpath(__file__)), 'bip39_english.txt'), 'r') - def eint_to_bytes(entint, entbits): a = hex(entint)[2:].rstrip('L').zfill(32) @@ -21,7 +18,7 @@ def eint_to_bytes(entint, entbits): return binascii.unhexlify(a) -def mnemonic_int_to_words(mint, mint_num_words, wordlist=wordlist_english): +def mnemonic_int_to_words(mint, mint_num_words, wordlist): backwords = [wordlist[(mint >> (11 * x)) & 0x7FF].strip() for x in range(mint_num_words)] return backwords[::-1] @@ -36,8 +33,8 @@ def entropy_cs(entbytes): # Call this to create a mnemonic phrase. # Possible values for entbytes (entropy bytes) are 16, 20, 24, 28 and 32 -def entropy_to_words(entbytes, wordlist=wordlist_english): - if entbytes not in [16, 20, 24, 28, 32]: +def entropy_to_words(entbytes, wordlist): + if len(entbytes) not in [16, 20, 24, 28, 32]: raise ValueError("entropy must be an element of [16, 20, 24, 28, 32]") entropy_size = 8 * len(entbytes) @@ -49,14 +46,14 @@ def entropy_to_words(entbytes, wordlist=wordlist_english): return mnemonic_int_to_words(mint, mint_num_words, wordlist) -def words_bisect(word, wordlist=wordlist_english): +def words_bisect(word, wordlist): lo = bisect_left(wordlist, word) hi = len(wordlist) - bisect_left(wordlist[:lo:-1], word) return lo, hi -def words_split(wordstr, wordlist=wordlist_english): +def words_split(wordstr, wordlist): def popword(wordstr, wordlist): for fwl in range(1, 9): w = wordstr[:fwl].strip() @@ -79,14 +76,14 @@ def popword(wordstr, wordlist): return words -def words_to_mnemonic_int(words, wordlist=wordlist_english): +def words_to_mnemonic_int(words, wordlist): if(isinstance(words, str)): words = words_split(words, wordlist) return sum([wordlist.index(w) << (11 * x) for x, w in enumerate(words[::-1])]) # BIP32 checksum verification -def words_verify(words, wordlist=wordlist_english): +def words_verify(words, wordlist): if(isinstance(words, str)): words = words_split(words, wordlist) @@ -105,7 +102,7 @@ def mnemonic_to_seed(mnemonic_phrase, passphrase=b''): try: from hashlib import pbkdf2_hmac - def pbkdf2_hmac_sha256(password, salt, iters=2048): + def pbkdf2_hmac_sha512(password, salt, iters=2048): return pbkdf2_hmac(hash_name='sha512', password=password, salt=salt, iterations=iters) except ImportError: @@ -113,17 +110,16 @@ def pbkdf2_hmac_sha256(password, salt, iters=2048): from Crypto.Protocol.KDF import PBKDF2 from Crypto.Hash import SHA512, HMAC - def pbkdf2_hmac_sha256(password, salt, iters=2048): + def pbkdf2_hmac_sha512(password, salt, iters=2048): return PBKDF2(password=password, salt=salt, dkLen=64, count=iters, prf=lambda p, s: HMAC.new(p, s, SHA512).digest()) except ImportError: try: - from pbkdf2 import PBKDF2 import hmac - def pbkdf2_hmac_sha256(password, salt, iters=2048): + def pbkdf2_hmac_sha512(password, salt, iters=2048): return PBKDF2(password, salt, iterations=iters, macmodule=hmac, digestmodule=hashlib.sha512).read(64) @@ -131,12 +127,12 @@ def pbkdf2_hmac_sha256(password, salt, iters=2048): except ImportError: raise RuntimeError("No implementation of pbkdf2 was found!") - return pbkdf2_hmac_sha256(password=mnemonic_phrase, + return pbkdf2_hmac_sha512(password=mnemonic_phrase, salt=b'mnemonic' + passphrase) # Relevant for Electrum style mnemonics -def words_mine(prefix, entbits, satisfunction, wordlist=wordlist_english, +def words_mine(prefix, entbits, satisfunction, wordlist, randombits=random.getrandbits): prefix_bits = len(prefix) * 11 mine_bits = entbits - prefix_bits diff --git a/eth_account/hdaccount/utils.py b/eth_account/hdaccount/utils.py index a7eb2960..061e940d 100644 --- a/eth_account/hdaccount/utils.py +++ b/eth_account/hdaccount/utils.py @@ -7,7 +7,9 @@ ) import time -import pyspecials +from eth_account.hdaccount import ( + pyspecials, +) # import base64 # import hmac diff --git a/eth_account/hdaccount/bip39_english.txt b/eth_account/hdaccount/wordlist/bip39_english.txt similarity index 100% rename from eth_account/hdaccount/bip39_english.txt rename to eth_account/hdaccount/wordlist/bip39_english.txt diff --git a/setup.py b/setup.py index 234f3f38..1717797b 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ author='Jason Carver', author_email='ethcalibur+pip@gmail.com', url='https://github.com/ethereum/eth-account', + package_data={'eth_account': ['hdaccount/wordlist/*.txt']}, include_package_data=True, install_requires=[ "attrdict>=2.0.0,<3", From 303acbb2462bd135bb5d48afe3bf0f10fea40cae Mon Sep 17 00:00:00 2001 From: sea212 Date: Sat, 1 Sep 2018 22:07:03 +0200 Subject: [PATCH 13/27] Sorted imports --- eth_account/hdaccount.py | 10 ++++------ eth_account/hdaccount/deterministic.py | 3 --- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 3bfb0a3a..ee8a613f 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -1,19 +1,15 @@ from os import ( urandom, ) - from os.path import ( - join as ojoin, dirname, + join as ojoin, realpath, ) from eth_account.datastructures import ( AttributeDict, ) -from eth_account.signers.base import ( - BaseAccount, -) from eth_account.hdaccount.deterministic import ( bip32_master_key, ) @@ -21,7 +17,9 @@ entropy_to_words, mnemonic_to_seed, ) - +from eth_account.signers.base import ( + BaseAccount, +) default_wordlist = ojoin(dirname(realpath(__file__)), 'hdaccount/wordlist/bip39_english.txt') diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py index b7d0fa17..5d170537 100644 --- a/eth_account/hdaccount/deterministic.py +++ b/eth_account/hdaccount/deterministic.py @@ -3,9 +3,6 @@ from eth_account.hdaccount import ( pyspecials, -) - -from eth_account.hdaccount import ( utils, ) From bdcf8cde9c1b4429395ebab0cc4c07dec4649d17 Mon Sep 17 00:00:00 2001 From: sea212 Date: Sun, 2 Sep 2018 20:57:51 +0200 Subject: [PATCH 14/27] Implemented account generator and deriveChild(...). Added derivation path argument support for init. Added and implemented decodePath(...) function to represent the derivation path as a string in format (m/)idx_0/.../idx_n . Added path property to return the string formatted derivation path. Added __repr__ and __str__, adjusted __eq__ and __hash__. Included (very basic) unit test for deriveChild(...) . Adapted function describing commenting style for every function. --- eth_account/hdaccount.py | 237 +++++++++++++++++++++---- eth_account/hdaccount/deterministic.py | 1 + 2 files changed, 200 insertions(+), 38 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index ee8a613f..bc705dfd 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -11,6 +11,8 @@ AttributeDict, ) from eth_account.hdaccount.deterministic import ( + bip32_ckd, + bip32_deserialize, bip32_master_key, ) from eth_account.hdaccount.mnemonic import ( @@ -30,21 +32,47 @@ class HDAccount(BaseAccount): This class manages BIP32 HD-Accounts for Ethereum ''' - def __init__(self, encoded_key=""): + def __init__(self, encoded_key: str = "", path=[]): ''' - The object is getting initialized here. You can pass no parameters, - in which case the object will be created containing no information. - You can create an account later by calling createAccount(...) in this case. - You can also initialize an account with a mnemonic code by using initAccount() - You can pass only an encoded key, in which case the class is initialized - by using the information contained within the encoded key. + Constructor for this class. Initializes an hd account generator and + if possible the encoded key and the derivation path. If no arguments are + specified, create a new account with createAccount(...) or initialize + an account given a mnemonic and an optional password with initAccount(...) + :param str encoded_key : (OPTIONAL) BIP32 serialized key + :param path : (OPTIONAL) derivation path, this is good to have + but not necessary. Only relevant if you specify + the encoded_key parameter + :type path : list as [idx_0, ..., idx_n] or str as either + "idx_0/.../idx_n" or "m/idx_0/.../idx_n" ''' # Contains derivation path. The class will automatically fill this self._path = [] + if isinstance(path, list): + if len(path) != 0: + for elem in path: + # Throws ValueError if elements are no base10 numbers + self._path.append(int(elem)) + elif isinstance(path, str): + self._path = self.decodePath() + else: + raise TypeError("path has to be a list or a string") + # Contains either encoded private key (hardened derivation possible) # or public key + + if not isinstance(encoded_key, str): + raise TypeError("Encoded Key has to be a string") + + # Before assigning the key, check if it has the correct format + if len(encoded_key) != 0: + try: + bip32_deserialize(encoded_key) + except Exception as e: + raise ValueError("encoded_key malformed: Not in bip32 serialized format.\n" + "Additional informations: %s" % e) + self.__key = encoded_key # Initiates the account generator @@ -52,61 +80,99 @@ def __init__(self, encoded_key=""): self.__accgen.send(None) # Magic number for hardened key derivation (see BIP32) - self.__const_hardened = 0x80000000 + self._const_hardened = 0x80000000 - def _accountGenerator(self, curindex: int = 0): + def _accountGenerator(self, cid: int = 0): ''' This is the account generator used to derive all desired children keys. It is ought to be used only internally. You can either send None to this generator, in which case it just increments the index and returns you the derived child object - for that index or you can send an index. + for that index or you can send an index. Use self.__accgen to interact + with this generator. + :param int cid: Child index, leave empty to continue with last index + 1 ''' # This variable will contain the object of the last derived child newacc = None + curindex = cid while True: cid = yield newacc + # cid will be type and value checked and. If it is greater zero, + # the next child index will be cid. If cid is const_magic_hardened, + # the current index will be incremented by the hardened child + # derivation constant + if cid is not None: if not isinstance(cid, int): raise TypeError("Invalid child index type. Excepted int") - curindex = cid - - # TODO derive child into a new HDAccount object - # z = HMAC-SHA512(chaincode, (maybe hardneed prefix) || compr. pubkey/privkey || index) - # derivation index = z[:16] - # new chaincode: z[16:] - # private key: privkey += index - # public key: publickey = pubkey ECC_ADD index ECC_MUL generator_point + # else + if cid >= 0: + curindex = cid + else: + # cid == -1 means that we increase the old index by the + # hardened constant + if cid != -1: + raise ValueError("Invalid child index %d" % cid) + + # else + if curindex < self._const_hardened: + curindex += self._const_hardened + + # Derive child. Will throw an error if hardened derivation is choosen + # and only a pubkey is present + newpath = self._path.copy() + newpath.append(curindex) + newacc = HDAccount(bip32_ckd(self.key, curindex), newpath) + # increment index and yield new HDAccount object curindex += 1 - def deriveChild(self, cid: int = None) -> "HDAccount": + def deriveChild(self, cid: int = None, hardened: bool = False) -> "HDAccount": ''' This function generates a new account by using the __accountGenerator function. You can specify an index. Not specifying an index leads to the usage of the old index + 1 - :param int cid: (OPTIONAL) contains child index + :param int cid : (OPTIONAL) contains child index. Leave empty to use + the previous index + 1 + :param bool hardened: (OPTIONAL) if set to true, 0x80000000 will be added + to cid which leads to hardened key derivation. + :returns : HDAccount object for the desired index + :rtype HDAccount ''' # catch invalid argument type error, otherwise it will break our generator - if cid is not None or not isinstance(cid, int): + if cid is not None and not isinstance(cid, int): raise TypeError("Excepted integer as index") - # TODO implement this gateway function for __accountGenerator(...) - # check if this object is initialized + if not isinstance(hardened, bool): + raise TypeError("Excepted bool for hardened") + + if isinstance(cid, int) and hardened is True: + if cid >= self._const_hardened: + raise ValueError("child index 0x%x is already >= 0x80000000 " + "(hardened), but hardened is set to True") + + cid += self._const_hardened + + if cid is not None and cid < 0: + raise ValueError("Negative child index not allowed") + + if cid is None and hardened is True: + cid = -1 + return self.__accgen.send(cid) def derivePath(self, path) -> "HDAccount": ''' This function receives a derivation path and returns an HDAccount object for the given path - :param path : contains the derivation path, example: "m/12H/1" or [0x8000000C, 1] - :type path : str or list - :returns : HDAccount for the desired path - :rtype : HDAccount + :param path : contains the derivation path, example: "m/12H/1" or [0x8000000C, 1] + :type path : str or list + :returns : HDAccount object for the desired path + :rtype HDAccount ''' pass @@ -114,10 +180,10 @@ def createAccount(self, password: str = "", ent_bytes: int = 32, wordlist: str = ''' This function initiates an account from scratch After completing the initiation it returns the mnemonic code - :param list password : additional password for mnemonic - :param list ent_bytes : amount of entropy bytes for generations of the + :param list password : (OPTIONAL) additional password for mnemonic + :param list ent_bytes : (OPTIONAL) amount of entropy bytes for generations of the mnemonic code has to be in [16, 20, 24, 28, 32] - :param str wordlist : path to wordlist including name of wordlist, + :param str wordlist : (OPTIONAL) path to wordlist including name of wordlist, defaults to english :returns : mnemonic code :rtype str @@ -142,8 +208,8 @@ def initAccount(self, mnemonic, password: str = ""): and an optional password :param mnemonic : the mnemonic code to derive the master keys from :type mnemonic : str or list - :param str password : the password required to successfully derive - the master keys from the mnemonic code + :param str password : (OPTIONAL) the password required to successfully + derive the master keys from the mnemonic code ''' if isinstance(mnemonic, str): @@ -153,8 +219,60 @@ def initAccount(self, mnemonic, password: str = ""): else: raise TypeError("Mnemonic has to be formated as a list or a string") - # create seed from mnemonic and derive key (in bip32 serialization format) + # Create seed from mnemonic and derive key (in bip32 serialization format) self.__key = bip32_master_key(seed) + self._path = [] + + def decodePath(self, path: str): + ''' + Converts the default string representation of a path to the internal + representation using a list. + :param str path : the string representation of the derivation path + :returns : list containing the derivation path + :rtype : list + ''' + + if not isinstance(path, str): + raise TypeError("Excepted path as string") + + pathlist = path.split("/") + + # Basic (incomplete) checks if the path fits the standard notation + if not path[0].isdigit(): + if not path[0].lower() == 'm': + raise ValueError("Excepted path in the form m/idx1/idx2/... or" + " idx1/idx2/...") + else: + if len(pathlist) == 1: + raise ValueError("Excepted path in the form m/idx1/idx2/... or" + " idx1/idx2/...") + + pathlist = pathlist[1:] if pathlist[0].lower() == 'm' else pathlist + return [int(elem) if not elem[-1].lower() == 'h' else + int(elem[:-1]) + self._const_hardened for elem in pathlist] + + @property + def path(self): + ''' + Returns the derivation path as a string of the form m/idx1/idx2 if the + current object contains a complete path or idx1/idx otherwise + :returns: derivation path + :rtype : str + ''' + converted_ints = [str(idx) if idx < self._const_hardened else + str(idx - self._const_hardened) + 'H' for idx in self._path] + + depth = 0 + + # In which derivation level is this object? + if self.__key != "": + depth = bip32_deserialize(self.__key)[1] + + # Full path available, so we can start with "m/" + if depth == len(self._path): + return "m/" + "/".join(converted_ints) + else: + return "/".join(converted_ints) @property def key(self): @@ -192,21 +310,43 @@ def signTransaction(self, transaction_dict: dict) -> AttributeDict: ''' pass + def __repr__(self): + ''' + Representation of this object. Use the output in your code to get + the same object + ''' + + return "eth_account.hdaccount.HDAccount(encoded_key={}, path={})"\ + .format(self.key, self._path) + + def __str__(self): + ''' + Human readable string represenation of this object + ''' + + return "Encoded key: {}\nDerivation Path: {}"\ + .format(self.key, self.path) + def __eq__(self, other): ''' Equality test between two accounts. Two accounts are considered the same if they are exactly the same type, and can sign for the same address. ''' - return type(self) == type(other) and self.address == other.address + return type(self) == type(other) and self.key == other.key and \ + self.path == other.path def __hash__(self): - return hash((type(self), self.address)) + ''' + Unique hash for this object + ''' + + return hash((type(self), self.key, self._path)) # Example on how to use this class if __name__ == "__main__": - # Create empty HDAccount object + # TEST 1: Create empty HDAccount object hdacc = HDAccount() # Create account @@ -215,7 +355,7 @@ def __hash__(self): mnemonic = hdacc.createAccount(pw) print("Mnemonic code: {}\nPassword: {}\nKey: {}\n".format(mnemonic, pw, hdacc.key)) - # Init account + # TEST 2: Init account print("Initializing new Account with the same mnemonic and password") hdacc2 = HDAccount() hdacc2.initAccount(mnemonic, pw) @@ -223,4 +363,25 @@ def __hash__(self): # Check if the creation of the account and the initialization share the same result assert(hdacc.key == hdacc2.key) - print("Success!") + print("\n") + + # TEST 3: Derive children using an index + # can we derived an hardened key? + newacc = hdacc.deriveChild(hardened=True) + print(newacc) + # can we set hardened=True twice without the hardened const being added twice? + newacc = hdacc.deriveChild(hardened=True) + print(newacc) + # no arguments possible? (includes testing auto increment) + newacc2 = newacc.deriveChild() + print(newacc2) + newacc2 = newacc.deriveChild() + print(newacc2) + # setting index manually possible? + newacc2 = newacc2.deriveChild(42) + print(newacc2) + + # TEST 4: Print empty HDAccount + print(HDAccount()) + + print("\nSuccess!\n") diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py index 5d170537..04a75262 100644 --- a/eth_account/hdaccount/deterministic.py +++ b/eth_account/hdaccount/deterministic.py @@ -169,6 +169,7 @@ def bip32_privtopub(data): return bip32_serialize(raw_bip32_privtopub(bip32_deserialize(data))) +# Use this for child derivation. Input BIP32 serialized key and index def bip32_ckd(data, i): return bip32_serialize(raw_bip32_ckd(bip32_deserialize(data), i)) From c4c6e1ae7c91be251232d3eacc1992b7346c2eab Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 3 Sep 2018 21:05:47 +0200 Subject: [PATCH 15/27] Implemented and tested derivePath(...). --- eth_account/hdaccount.py | 43 +++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index bc705dfd..048a715b 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -169,12 +169,26 @@ def derivePath(self, path) -> "HDAccount": ''' This function receives a derivation path and returns an HDAccount object for the given path - :param path : contains the derivation path, example: "m/12H/1" or [0x8000000C, 1] + :param path : contains the derivation path, either formated as + "(m/)idx_0/.../idx_n" or [idx_0, ..., idx_n] :type path : str or list :returns : HDAccount object for the desired path :rtype HDAccount ''' - pass + + if isinstance(path, list): + enc_path = path + elif isinstance(path, str): + enc_path = self.decodePath(path) + else: + raise TypeError("path must be list or str in format (m/)/idx_1/.../idx_n") + + hdacc = self + + for idx in enc_path: + hdacc = hdacc.deriveChild(idx) + + return hdacc def createAccount(self, password: str = "", ent_bytes: int = 32, wordlist: str = None) -> str: ''' @@ -347,6 +361,7 @@ def __hash__(self): # Example on how to use this class if __name__ == "__main__": # TEST 1: Create empty HDAccount object + print("--- TEST 1: Create HDAccount ---\n\n") hdacc = HDAccount() # Create account @@ -356,17 +371,17 @@ def __hash__(self): print("Mnemonic code: {}\nPassword: {}\nKey: {}\n".format(mnemonic, pw, hdacc.key)) # TEST 2: Init account - print("Initializing new Account with the same mnemonic and password") + print("\n\n--- TEST2: Init account ---\n\n") hdacc2 = HDAccount() hdacc2.initAccount(mnemonic, pw) print("Key: ", hdacc2.key) # Check if the creation of the account and the initialization share the same result assert(hdacc.key == hdacc2.key) - print("\n") # TEST 3: Derive children using an index # can we derived an hardened key? + print("\n\n--- TEST3: Derive children ---\n\n") newacc = hdacc.deriveChild(hardened=True) print(newacc) # can we set hardened=True twice without the hardened const being added twice? @@ -382,6 +397,24 @@ def __hash__(self): print(newacc2) # TEST 4: Print empty HDAccount - print(HDAccount()) + # print("\n\n--- TEST4: Print empty children ---\n\n") + # print(HDAccount() + "\n") + + # Test 5: Derive account using a path + print("\n\n--- TEST 5: Derive path ---\n\n") + path_from_test3_list = newacc2._path + path_from_test3_str = newacc2.path + # path as list + newacc3 = HDAccount() + newacc3.initAccount(mnemonic, pw) + newacc3 = newacc3.derivePath(path_from_test3_list) + print(newacc3) + assert(newacc3 == newacc2) + # path as string + newacc3 = HDAccount() + newacc3.initAccount(mnemonic, pw) + newacc3 = newacc3.derivePath(path_from_test3_str) + print(newacc3) + assert(newacc3 == newacc2) print("\nSuccess!\n") From e2be5a149a81c6a53f0867f7e31fa26f395d3d9d Mon Sep 17 00:00:00 2001 From: sea212 Date: Mon, 3 Sep 2018 21:13:04 +0200 Subject: [PATCH 16/27] linter --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 048a715b..9b20ff79 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -182,7 +182,7 @@ def derivePath(self, path) -> "HDAccount": enc_path = self.decodePath(path) else: raise TypeError("path must be list or str in format (m/)/idx_1/.../idx_n") - + hdacc = self for idx in enc_path: From fdedcb9e396adc95fff6d24549464900ed46a885 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 22:14:09 +0200 Subject: [PATCH 17/27] Implemented and tested: removePrivateKey(...), address, signMessage(...), signTransaction(...) . Added value check to deterministic.py --- eth_account/hdaccount.py | 192 ++++++++++++++++++++----- eth_account/hdaccount/deterministic.py | 9 +- 2 files changed, 166 insertions(+), 35 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 9b20ff79..f91a0195 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -6,6 +6,14 @@ join as ojoin, realpath, ) +from eth_keys import ( + KeyAPI, +) + + +from eth_account.account import ( + Account, +) from eth_account.datastructures import ( AttributeDict, @@ -14,11 +22,16 @@ bip32_ckd, bip32_deserialize, bip32_master_key, + bip32_privtopub, + PRIVATE, ) from eth_account.hdaccount.mnemonic import ( entropy_to_words, mnemonic_to_seed, ) +from eth_account.hdaccount.utils import ( + decompress, +) from eth_account.signers.base import ( BaseAccount, ) @@ -46,6 +59,9 @@ def __init__(self, encoded_key: str = "", path=[]): "idx_0/.../idx_n" or "m/idx_0/.../idx_n" ''' + # Magic number for hardened key derivation (see BIP32) + self._const_hardened = 0x80000000 + # Contains derivation path. The class will automatically fill this self._path = [] @@ -55,7 +71,7 @@ def __init__(self, encoded_key: str = "", path=[]): # Throws ValueError if elements are no base10 numbers self._path.append(int(elem)) elif isinstance(path, str): - self._path = self.decodePath() + self._path = self.decodePath(path) else: raise TypeError("path has to be a list or a string") @@ -79,9 +95,6 @@ def __init__(self, encoded_key: str = "", path=[]): self.__accgen = self._accountGenerator() self.__accgen.send(None) - # Magic number for hardened key derivation (see BIP32) - self._const_hardened = 0x80000000 - def _accountGenerator(self, cid: int = 0): ''' This is the account generator used to derive all desired @@ -126,11 +139,11 @@ def _accountGenerator(self, cid: int = 0): # and only a pubkey is present newpath = self._path.copy() newpath.append(curindex) - newacc = HDAccount(bip32_ckd(self.key, curindex), newpath) + newacc = HDAccount(bip32_ckd(self.__key, curindex), newpath) # increment index and yield new HDAccount object curindex += 1 - def deriveChild(self, cid: int = None, hardened: bool = False) -> "HDAccount": + def deriveChild(self, cid=None, hardened=False): ''' This function generates a new account by using the __accountGenerator function. You can specify an index. @@ -139,23 +152,18 @@ def deriveChild(self, cid: int = None, hardened: bool = False) -> "HDAccount": the previous index + 1 :param bool hardened: (OPTIONAL) if set to true, 0x80000000 will be added to cid which leads to hardened key derivation. - :returns : HDAccount object for the desired index :rtype HDAccount ''' # catch invalid argument type error, otherwise it will break our generator if cid is not None and not isinstance(cid, int): - raise TypeError("Excepted integer as index") + raise TypeError("Excepted integer or None as index") if not isinstance(hardened, bool): raise TypeError("Excepted bool for hardened") - if isinstance(cid, int) and hardened is True: - if cid >= self._const_hardened: - raise ValueError("child index 0x%x is already >= 0x80000000 " - "(hardened), but hardened is set to True") - - cid += self._const_hardened + if isinstance(cid, int) and hardened is True and not cid >= self._const_hardened: + cid += self._const_hardened if cid is not None and cid < 0: raise ValueError("Negative child index not allowed") @@ -265,6 +273,16 @@ def decodePath(self, path: str): return [int(elem) if not elem[-1].lower() == 'h' else int(elem[:-1]) + self._const_hardened for elem in pathlist] + def removePrivateKey(self): + ''' + Removes a private key from this object and replaces it with a public key. + From this moment on, only public keys can be derived from this object. + ''' + if (self.__key == ""): + return + + self.__key = bip32_privtopub(self.__key) + @property def path(self): ''' @@ -301,33 +319,99 @@ def key(self): @property def address(self) -> str: ''' - The checksummed public address for this account. + Get the checksummed address of this hd account + :returns: the checksummed public address for this account. + :rtype : str .. code-block:: python >>> my_account.address "0xF0109fC8DF283027b6285cc889F5aA624EaC1F55" ''' - pass - def signHash(self, message_hash: bytes) -> AttributeDict: + rawtuple = bip32_deserialize(self.__key) + + key = rawtuple[-1] + + if rawtuple[0] in PRIVATE: + # slice the last byte, since it is the WIF-Compressed information + key = KeyAPI.PrivateKey(key[:-1]).public_key + else: + # remove 04 prefix for KeyAPI + key = KeyAPI.PublicKey(decompress(key)[1:]) + + return key.to_checksum_address() + + def signHash(self, message_hash): ''' - Sign the hash of a message, as in :meth:`~eth_account.account.Account.signHash` - but without specifying the private key. - :param bytes message_hash: 32 byte hash of the message to sign + Sign the hash provided. + + .. WARNING:: *Never* sign a hash that you didn't generate, + it can be an arbitrary transaction. For example, it might + send all of your account's ether to an attacker. + + If you would like compatibility with + :meth:`w3.eth.sign() ` + you can use :meth:`~eth_account.messages.defunct_hash_message`. + + Several other message standards are proposed, but none have a clear + consensus. You'll need to manually comply with any of those message standards manually. + + :param message_hash: the 32-byte message hash to be signed + :type message_hash: hex str, bytes or int + :param private_key: the key to sign the message with + :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` + :returns: Various details about the signature - most + importantly the fields: v, r, and s + :rtype: ~eth_account.datastructures.AttributeDict ''' - pass + + rawtuple = bip32_deserialize(self.__key) + + if rawtuple[0] in PRIVATE: + # slice the last byte, since it is the WIF-Compressed information + return Account.signHash(message_hash, rawtuple[5][:-1]) + + if bip32_deserialize(self.__key)[0] not in PRIVATE: + raise RuntimeError("Cannot sign, only the public key is available") def signTransaction(self, transaction_dict: dict) -> AttributeDict: ''' - Sign a transaction, as in :meth:`~eth_account.account.Account.signTransaction` - but without specifying the private key. - :param dict transaction_dict: transaction with all fields specified + Sign the hash provided. + + .. WARNING:: *Never* sign a hash that you didn't generate, + it can be an arbitrary transaction. For example, it might + send all of your account's ether to an attacker. + + If you would like compatibility with + :meth:`w3.eth.sign() ` + you can use :meth:`~eth_account.messages.defunct_hash_message`. + + Several other message standards are proposed, but none have a clear + consensus. You'll need to manually comply with any of those message standards manually. + + :param message_hash: the 32-byte message hash to be signed + :type message_hash: hex str, bytes or int + :param private_key: the key to sign the message with + :type private_key: hex str, bytes, int or :class:`eth_keys.datatypes.PrivateKey` + :returns: Various details about the signature - most + importantly the fields: v, r, and s + :rtype: ~eth_account.datastructures.AttributeDict ''' - pass + + rawtuple = bip32_deserialize(self.__key) + + if rawtuple[0] in PRIVATE: + # slice the last byte, since it is the WIF-Compressed information + return Account.signTransaction(transaction_dict, rawtuple[5][:-1]) + + if bip32_deserialize(self.__key)[0] not in PRIVATE: + raise RuntimeError("Cannot sign, only the public key is available") def __repr__(self): ''' Representation of this object. Use the output in your code to get the same object + :returns: string that shows how to get the same object + :rtype : str ''' return "eth_account.hdaccount.HDAccount(encoded_key={}, path={})"\ @@ -336,16 +420,20 @@ def __repr__(self): def __str__(self): ''' Human readable string represenation of this object + :returns: string that represents the object in a human readable format + :rtype : str ''' - return "Encoded key: {}\nDerivation Path: {}"\ - .format(self.key, self.path) + return "Encoded key: {}\nChecksummed address: {}\nDerivation Path: {}"\ + .format(self.key, self.address, self.path) def __eq__(self, other): ''' Equality test between two accounts. Two accounts are considered the same if they are exactly the same type, and can sign for the same address. + :returns: boolean that indicates whether other is equal to this object + :rtype : bool ''' return type(self) == type(other) and self.key == other.key and \ self.path == other.path @@ -353,6 +441,8 @@ def __eq__(self, other): def __hash__(self): ''' Unique hash for this object + :returns: unique hash of this object + :rtype : int ''' return hash((type(self), self.key, self._path)) @@ -360,7 +450,6 @@ def __hash__(self): # Example on how to use this class if __name__ == "__main__": - # TEST 1: Create empty HDAccount object print("--- TEST 1: Create HDAccount ---\n\n") hdacc = HDAccount() @@ -370,7 +459,6 @@ def __hash__(self): mnemonic = hdacc.createAccount(pw) print("Mnemonic code: {}\nPassword: {}\nKey: {}\n".format(mnemonic, pw, hdacc.key)) - # TEST 2: Init account print("\n\n--- TEST2: Init account ---\n\n") hdacc2 = HDAccount() hdacc2.initAccount(mnemonic, pw) @@ -379,7 +467,6 @@ def __hash__(self): # Check if the creation of the account and the initialization share the same result assert(hdacc.key == hdacc2.key) - # TEST 3: Derive children using an index # can we derived an hardened key? print("\n\n--- TEST3: Derive children ---\n\n") newacc = hdacc.deriveChild(hardened=True) @@ -396,11 +483,17 @@ def __hash__(self): newacc2 = newacc2.deriveChild(42) print(newacc2) - # TEST 4: Print empty HDAccount - # print("\n\n--- TEST4: Print empty children ---\n\n") - # print(HDAccount() + "\n") + print("\n\n--- TEST4: Remove private key (only public key derivation possible) ---\n\n") + pubacc = HDAccount() + pubacc.createAccount() + # here we derive a child but self.__key is a xprv key + pubacc.removePrivateKey() + pubacc = pubacc.deriveChild() + print(pubacc) + # here we derive a child and self.__key is a xpub key + pubacc = pubacc.deriveChild(42) + print(pubacc) - # Test 5: Derive account using a path print("\n\n--- TEST 5: Derive path ---\n\n") path_from_test3_list = newacc2._path path_from_test3_str = newacc2.path @@ -417,4 +510,35 @@ def __hash__(self): print(newacc3) assert(newacc3 == newacc2) + print("\n\n--- TEST 6: Get address from xprv and xpub encoded keys ---\n\n") + + print(newacc3.address) + print(pubacc.address) print("\nSuccess!\n") + + print("\n\n--- TEST 7: Sign message hash ---\n\n") + randnum = urandom(32) + msg = hex(int.from_bytes(randnum, "big")) + print("signing key : %s" % newacc3.key) + print("hash to sign: %s" % msg) + print("Result:") + print(newacc3.signHash(msg)) + + print("\n\n--- TEST 8: Create and sign Transaction ---\n\n") + testkey = "xprv9y8Gw5q3qFv42CiuohDV6L8gY9VHjs74dMKos2pVp74vyHFRiTHwPHFDAEgz" \ + "YDYDQYEh2yahi4Jga6qQn3q45yzvPcPibUaKNRrkTbsDHkK" + testpath = "m/1H/1/42" + testacc = HDAccount(testkey, testpath) + transaction = { + 'to': '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + 'value': 1000000000, + 'gas': 21000, + 'gasPrice': 2000000000, + 'nonce': 0, + 'chainId': 4 + } + print(testacc) + print("transaction to sign: ") + print(transaction) + print("Signed Transaction:") + print(testacc.signTransaction(transaction)) diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py index 04a75262..e9c6175b 100644 --- a/eth_account/hdaccount/deterministic.py +++ b/eth_account/hdaccount/deterministic.py @@ -120,6 +120,10 @@ def raw_bip32_ckd(rawtuple, i): def raw_bip32_privtopub(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple + + if vbytes in PUBLIC: + return (vbytes, depth, fingerprint, i, chaincode, key) + newvbytes = MAINNET_PUBLIC if vbytes == MAINNET_PRIVATE else TESTNET_PUBLIC return (newvbytes, depth, fingerprint, i, chaincode, utils.privtopub(key)) @@ -188,7 +192,7 @@ def bip32_bin_extract_key(data): def bip32_extract_key(data): return pyspecials.safe_hexlify(bip32_deserialize(data)[-1]) - +''' def bip32_descend(*args): if len(args) == 2 and isinstance(args[1], list): key, path = args @@ -199,12 +203,14 @@ def bip32_descend(*args): key = bip32_ckd(key, p) return bip32_extract_key(key) +''' # Exploits the same vulnerability as above in Electrum wallets # Takes a BIP32 pubkey and one of the child privkeys of its corresponding # privkey and returns the BIP32 privkey associated with that pubkey +''' def crack_bip32_privkey(parent_pub, priv): dsppub = bip32_deserialize(parent_pub) dspriv = bip32_deserialize(priv) @@ -227,3 +233,4 @@ def coinvault_priv_to_bip32(*args): I2 = ''.join(map(chr, vals[35:67])) I3 = ''.join(map(chr, vals[72:104])) return bip32_serialize((MAINNET_PRIVATE, 0, b'\x00' * 4, 0, I2, I3 + b'\x01')) +''' From b54194150d56e8b7fe4597946872b6bdb6305b29 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 22:16:54 +0200 Subject: [PATCH 18/27] Sorted imports --- eth_account/hdaccount.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index f91a0195..f88a4ec1 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -6,24 +6,19 @@ join as ojoin, realpath, ) -from eth_keys import ( - KeyAPI, -) - from eth_account.account import ( Account, ) - from eth_account.datastructures import ( AttributeDict, ) from eth_account.hdaccount.deterministic import ( + PRIVATE, bip32_ckd, bip32_deserialize, bip32_master_key, bip32_privtopub, - PRIVATE, ) from eth_account.hdaccount.mnemonic import ( entropy_to_words, @@ -35,6 +30,9 @@ from eth_account.signers.base import ( BaseAccount, ) +from eth_keys import ( + KeyAPI, +) default_wordlist = ojoin(dirname(realpath(__file__)), 'hdaccount/wordlist/bip39_english.txt') From 4b36aa38ded95536ab2e6e503154616cdaf8e8f2 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 22:23:30 +0200 Subject: [PATCH 19/27] linter adjustions --- eth_account/hdaccount/deterministic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eth_account/hdaccount/deterministic.py b/eth_account/hdaccount/deterministic.py index e9c6175b..c681cee8 100644 --- a/eth_account/hdaccount/deterministic.py +++ b/eth_account/hdaccount/deterministic.py @@ -120,7 +120,7 @@ def raw_bip32_ckd(rawtuple, i): def raw_bip32_privtopub(rawtuple): vbytes, depth, fingerprint, i, chaincode, key = rawtuple - + if vbytes in PUBLIC: return (vbytes, depth, fingerprint, i, chaincode, key) @@ -192,6 +192,7 @@ def bip32_bin_extract_key(data): def bip32_extract_key(data): return pyspecials.safe_hexlify(bip32_deserialize(data)[-1]) + ''' def bip32_descend(*args): if len(args) == 2 and isinstance(args[1], list): From 839617b74d52d2145ca958c689147d6a053db9d4 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 22:34:49 +0200 Subject: [PATCH 20/27] CirleCI claims that the imports of hdaccount.py are not sorted, my isort tells me the contrary --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index f88a4ec1..0490f6df 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -427,7 +427,7 @@ def __str__(self): def __eq__(self, other): ''' - Equality test between two accounts. + Equality test between two accounts. Two accounts are considered the same if they are exactly the same type, and can sign for the same address. :returns: boolean that indicates whether other is equal to this object From c5529ce670981da3c97d25967dbd437e279de0d0 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 22:36:21 +0200 Subject: [PATCH 21/27] style update --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 0490f6df..f88a4ec1 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -427,7 +427,7 @@ def __str__(self): def __eq__(self, other): ''' - Equality test between two accounts. + Equality test between two accounts. Two accounts are considered the same if they are exactly the same type, and can sign for the same address. :returns: boolean that indicates whether other is equal to this object From 08c92cd4567457015efbe51d3c6fd020476cf5c2 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 23:01:26 +0200 Subject: [PATCH 22/27] style --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index f88a4ec1..c8045f5b 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -6,10 +6,10 @@ join as ojoin, realpath, ) - from eth_account.account import ( Account, ) + from eth_account.datastructures import ( AttributeDict, ) From 01f54611151466a775a0ad53d62c820d0c0e6837 Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 23:03:06 +0200 Subject: [PATCH 23/27] style --- eth_account/hdaccount.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index c8045f5b..6e286a98 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -6,10 +6,13 @@ join as ojoin, realpath, ) +from eth_keys import ( + KeyAPI, +) + from eth_account.account import ( Account, ) - from eth_account.datastructures import ( AttributeDict, ) @@ -30,9 +33,6 @@ from eth_account.signers.base import ( BaseAccount, ) -from eth_keys import ( - KeyAPI, -) default_wordlist = ojoin(dirname(realpath(__file__)), 'hdaccount/wordlist/bip39_english.txt') From 9316a827834c33ed23af2bb577af98ab4379deaf Mon Sep 17 00:00:00 2001 From: sea212 Date: Tue, 4 Sep 2018 23:05:48 +0200 Subject: [PATCH 24/27] style --- eth_account/hdaccount.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 6e286a98..c2f33e9a 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -1,3 +1,6 @@ +from eth_keys import ( + KeyAPI, +) from os import ( urandom, ) @@ -6,9 +9,6 @@ join as ojoin, realpath, ) -from eth_keys import ( - KeyAPI, -) from eth_account.account import ( Account, From 362de9a9d23fdbf55c10f53156bb6751cb18bf45 Mon Sep 17 00:00:00 2001 From: sea212 Date: Wed, 5 Sep 2018 01:47:58 +0200 Subject: [PATCH 25/27] HDAccount class completed and tested --- eth_account/hdaccount.py | 197 ++++++++++++++++++++++++++---- eth_account/hdaccount/mnemonic.py | 20 ++- eth_account/hdaccount/utils.py | 140 --------------------- 3 files changed, 189 insertions(+), 168 deletions(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index c2f33e9a..3deb5fbf 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -1,6 +1,4 @@ -from eth_keys import ( - KeyAPI, -) +from math import ceil from os import ( urandom, ) @@ -13,9 +11,6 @@ from eth_account.account import ( Account, ) -from eth_account.datastructures import ( - AttributeDict, -) from eth_account.hdaccount.deterministic import ( PRIVATE, bip32_ckd, @@ -33,9 +28,12 @@ from eth_account.signers.base import ( BaseAccount, ) +from eth_keys import ( + KeyAPI, +) default_wordlist = ojoin(dirname(realpath(__file__)), - 'hdaccount/wordlist/bip39_english.txt') + "hdaccount/wordlist/bip39_english.txt") class HDAccount(BaseAccount): @@ -43,7 +41,7 @@ class HDAccount(BaseAccount): This class manages BIP32 HD-Accounts for Ethereum ''' - def __init__(self, encoded_key: str = "", path=[]): + def __init__(self, encoded_key="", path=[]): ''' Constructor for this class. Initializes an hd account generator and if possible the encoded key and the derivation path. If no arguments are @@ -93,7 +91,7 @@ def __init__(self, encoded_key: str = "", path=[]): self.__accgen = self._accountGenerator() self.__accgen.send(None) - def _accountGenerator(self, cid: int = 0): + def _accountGenerator(self, cid=0): ''' This is the account generator used to derive all desired children keys. It is ought to be used only internally. @@ -171,7 +169,7 @@ def deriveChild(self, cid=None, hardened=False): return self.__accgen.send(cid) - def derivePath(self, path) -> "HDAccount": + def derivePath(self, path): ''' This function receives a derivation path and returns an HDAccount object for the given path @@ -196,7 +194,7 @@ def derivePath(self, path) -> "HDAccount": return hdacc - def createAccount(self, password: str = "", ent_bytes: int = 32, wordlist: str = None) -> str: + def createAccount(self, password="", ent_bytes=32, wordlist=None): ''' This function initiates an account from scratch After completing the initiation it returns the mnemonic code @@ -222,7 +220,7 @@ def createAccount(self, password: str = "", ent_bytes: int = 32, wordlist: str = self.initAccount(mnemonic, password) return " ".join(mnemonic) - def initAccount(self, mnemonic, password: str = ""): + def initAccount(self, mnemonic="", password="", seed=b''): ''' This function initiates an account by using a mnemonic code and an optional password @@ -230,20 +228,40 @@ def initAccount(self, mnemonic, password: str = ""): :type mnemonic : str or list :param str password : (OPTIONAL) the password required to successfully derive the master keys from the mnemonic code + :param seed : the seed to derive the master key from (alternative) + :type seed : str (hex formated) or bytes ''' - if isinstance(mnemonic, str): - seed = mnemonic_to_seed(mnemonic.encode("utf-8"), password.encode("utf-8")) - elif isinstance(mnemonic, list): - seed = mnemonic_to_seed(" ".join(mnemonic).encode("utf-8"), password.encode("utf-8")) + if not (isinstance(mnemonic, str) or isinstance(mnemonic, list)) \ + or not isinstance(password, str): + raise TypeError("Mnemonic has to be formated as a list or a string " + " and password as string") + + if not isinstance(seed, str) and not isinstance(seed, bytes): + raise TypeError("Seed has to be a string or bytes") + + if (mnemonic != "" and seed != b'') or (mnemonic == "" and seed == b''): + raise ValueError("Either a mnemonic or a seed has to be defined") + + if mnemonic != "": + if isinstance(mnemonic, str): + drvseed = mnemonic_to_seed(mnemonic.encode("utf-8"), password.encode("utf-8")) + else: + drvseed = mnemonic_to_seed(" ".join(mnemonic).encode("utf-8"), + password.encode("utf-8")) else: - raise TypeError("Mnemonic has to be formated as a list or a string") + if isinstance(seed, bytes): + drvseed = seed + else: + lenbytes = ceil(len(seed[2:]) / 2) if seed.startswith("0x") \ + else ceil(len(seed) / 2) + drvseed = int(seed, 16).to_bytes(lenbytes, "big") # Create seed from mnemonic and derive key (in bip32 serialization format) - self.__key = bip32_master_key(seed) + self.__key = bip32_master_key(drvseed) self._path = [] - def decodePath(self, path: str): + def decodePath(self, path): ''' Converts the default string representation of a path to the internal representation using a list. @@ -298,6 +316,9 @@ def path(self): if self.__key != "": depth = bip32_deserialize(self.__key)[1] + if depth == 0: + return "m" + # Full path available, so we can start with "m/" if depth == len(self._path): return "m/" + "/".join(converted_ints) @@ -315,7 +336,7 @@ def key(self): return self.__key @property - def address(self) -> str: + def address(self): ''' Get the checksummed address of this hd account :returns: the checksummed public address for this account. @@ -371,7 +392,7 @@ def signHash(self, message_hash): if bip32_deserialize(self.__key)[0] not in PRIVATE: raise RuntimeError("Cannot sign, only the public key is available") - def signTransaction(self, transaction_dict: dict) -> AttributeDict: + def signTransaction(self, transaction_dict): ''' Sign the hash provided. @@ -446,7 +467,7 @@ def __hash__(self): return hash((type(self), self.key, self._path)) -# Example on how to use this class +# Simple tests if __name__ == "__main__": print("--- TEST 1: Create HDAccount ---\n\n") hdacc = HDAccount() @@ -540,3 +561,135 @@ def __hash__(self): print(transaction) print("Signed Transaction:") print(testacc.signTransaction(transaction)) + + print("\n\n--- TEST 9: BIP32 Testvector 1 ---\n\n") + rootseed1 = "000102030405060708090a0b0c0d0e0f" + testvector1 = [ + { + "path": 'm', + "xpub": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ES" + "FjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", + "xprv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvv" + "NKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" + }, { + "path": "m/0H", + "xpub": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwB" + "ZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", + "xprv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL" + "5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7" + }, { + "path": "m/0H/1", + "xpub": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWb" + "WMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", + "xprv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw" + "1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs" + }, { + "path": "m/0H/1/2H", + "xpub": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3" + "No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", + "xprv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7Fwu" + "EzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM" + }, { + "path": "m/0H/1/2H/2", + "xpub": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjT" + "Awm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", + "xprv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQM" + "uPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" + }, { + "path": "m/0H/1/2H/2/1000000000", + "xpub": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEc" + "YFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", + "xprv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8km" + "HScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76" + } + ] + + rootacc = HDAccount() + rootacc.initAccount(seed=rootseed1) + + for vector in testvector1: + vectoracc = rootacc.derivePath(vector["path"]) + assert(vectoracc.key == vector["xprv"]) + assert(bip32_privtopub(vectoracc.key) == vector["xpub"]) + + print("Success!") + + print("\n\n--- TEST 10: BIP32 Testvector 2 ---\n\n") + rootseed2 = "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c"\ + "999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542" + testvector2 = [ + { + "path": 'm', + "xpub": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu" + "8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", + "xprv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3p" + "Gz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U" + }, { + "path": "m/0", + "xpub": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXU" + "bC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", + "xprv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3d" + "KYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt" + }, { + "path": "m/0/2147483647H", + "xpub": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85" + "ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", + "xprv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeE" + "g2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9" + }, { + "path": "m/0/2147483647H/1", + "xpub": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVv" + "mdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", + "xprv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25" + "UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef" + }, { + "path": "m/0/2147483647H/1/2147483646H", + "xpub": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZ" + "RkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", + "xprv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7i" + "Axn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc" + }, { + "path": "m/0/2147483647H/1/2147483646H/2", + "xpub": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbd" + "pq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", + "xprv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7n" + "adnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j" + } + ] + + rootacc.initAccount(seed=rootseed2) + + for vector in testvector2: + vectoracc = rootacc.derivePath(vector["path"]) + assert(vectoracc.key == vector["xprv"]) + assert(bip32_privtopub(vectoracc.key) == vector["xpub"]) + + print("Success!") + + print("\n\n--- TEST 11: BIP32 Testvector 3 ---\n\n") + rootseed3 = "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45"\ + "d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be" + testvector3 = [ + { + "path": 'm', + "xpub": "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP" + "7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13", + "xprv": "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KX" + "p1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6" + }, { + "path": "m/0H", + "xpub": "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWg" + "qbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y", + "xprv": "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2q" + "aMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L" + } + ] + + rootacc.initAccount(seed=rootseed3) + + for vector in testvector3: + vectoracc = rootacc.derivePath(vector["path"]) + assert(vectoracc.key == vector["xprv"]) + assert(bip32_privtopub(vectoracc.key) == vector["xpub"]) + + print("All tests were successful!") diff --git a/eth_account/hdaccount/mnemonic.py b/eth_account/hdaccount/mnemonic.py index 0c00d94f..13655b32 100644 --- a/eth_account/hdaccount/mnemonic.py +++ b/eth_account/hdaccount/mnemonic.py @@ -153,15 +153,23 @@ def words_mine(prefix, entbits, satisfunction, wordlist, if __name__ == "__main__": import json - - testvectors = json.load(open('vectors.json', 'r')) + import os + thisdir = os.path.dirname(os.path.realpath(__file__)) + vectors = os.path.join(thisdir, "vectors.json") + wordlist = os.path.join(thisdir, "wordlist/bip39_english.txt") + testvectors = json.load(open(vectors, 'r')) passed = True + with open(wordlist, 'r') as fwordlist: + wlcnt = fwordlist.readlines() + for v in testvectors['english']: ebytes = binascii.unhexlify(v[0]) - w = ' '.join(entropy_to_words(ebytes)) - seed = mnemonic_to_seed(w, passphrase='TREZOR') + w = ' '.join(entropy_to_words(ebytes, wlcnt)) + seed = mnemonic_to_seed(w.encode("utf-8"), 'TREZOR'.encode("utf-8")) + # print("our seed: %s" % binascii.hexlify(seed).decode("utf-8")) + # print("testvector: %s" % v[2]) passed = passed and w == v[1] - passed = passed and binascii.hexlify(seed) == v[2] + passed = passed and binascii.hexlify(seed).decode("utf-8") == v[2] - print("Tests %s." % ("Passed" if passed else "Failed")) + print("Test %s" % ("Passed" if passed else "Failed")) diff --git a/eth_account/hdaccount/utils.py b/eth_account/hdaccount/utils.py index 061e940d..981bb886 100644 --- a/eth_account/hdaccount/utils.py +++ b/eth_account/hdaccount/utils.py @@ -375,15 +375,6 @@ def privkey_to_pubkey(privkey): privtopub = privkey_to_pubkey -''' -def privkey_to_address(priv, magicbyte=0): - return pubkey_to_address(privkey_to_pubkey(priv), magicbyte) - - -privtoaddr = privkey_to_address -''' - - def neg_pubkey(pubkey): f = get_pubkey_format(pubkey) pubkey = decode_pubkey(pubkey, f) @@ -541,20 +532,6 @@ def b58check_to_hex(inp): return pyspecials.safe_hexlify(b58check_to_bin(inp)) -''' -def pubkey_to_address(pubkey, magicbyte=0): - if isinstance(pubkey, (list, tuple)): - pubkey = encode_pubkey(pubkey, 'bin') - if len(pubkey) in [66, 130]: - return pyspecials.bin_to_b58check( - bin_hash160(binascii.unhexlify(pubkey)), magicbyte) - return bin_to_b58check(bin_hash160(pubkey), magicbyte) - - -pubtoaddr = pubkey_to_address -''' - - def is_privkey(priv): try: get_privkey_format(priv) @@ -576,123 +553,6 @@ def is_address(addr): return bool(ADDR_RE.match(addr)) -# EDCSA - -''' -def encode_sig(v, r, s): - vb, rb, sb = pyspecials.from_int_to_byte(v), pyspecials.encode(r, 256), \ - pyspecials.encode(s, 256) - - result = base64.b64encode(vb + b'\x00' * (32 - len(rb)) + rb + - b'\x00' * (32 - len(sb)) + sb) - return result if version_info.major == 2 else str(result, 'utf-8') - - -def decode_sig(sig): - bytez = base64.b64decode(sig) - return pyspecials.from_byte_to_int(bytez[0]), pyspecials.decode(bytez[1:33], 256), \ - pyspecials.decode(bytez[33:], 256) - -# https://tools.ietf.org/html/rfc6979#section-3.2 - - -def deterministic_generate_k(msghash, priv): - v = b'\x01' * 32 - k = b'\x00' * 32 - priv = encode_privkey(priv, 'bin') - msghash = encode(hash_to_int(msghash), 256, 32) - k = hmac.new(k, v+b'\x00' + priv + msghash, hashlib.sha256).digest() - v = hmac.new(k, v, hashlib.sha256).digest() - k = hmac.new(k, v+b'\x01' + priv + msghash, hashlib.sha256).digest() - v = hmac.new(k, v, hashlib.sha256).digest() - return pyspecials.decode(hmac.new(k, v, hashlib.sha256).digest(), 256) - - -def ecdsa_raw_sign(msghash, priv): - - z = hash_to_int(msghash) - k = deterministic_generate_k(msghash, priv) - - r, y = fast_multiply(G, k) - s = inv(k, N) * (z + r*decode_privkey(priv)) % N - - v, r, s = 27 + ((y % 2) ^ (0 if s * 2 < N else 1)), r, s if s * 2 < N else N - s - - if 'compressed' in get_privkey_format(priv): - v += 4 - - return v, r, s - - -def ecdsa_sign(msg, priv): - v, r, s = ecdsa_raw_sign(electrum_sig_hash(msg), priv) - sig = encode_sig(v, r, s) - - assert ecdsa_verify(msg, sig, privtopub(priv)), \ - "Bad Sig!\t %s\nv = %d\n,r = %d\ns = %d" % (sig, v, r, s) - - return sig - - -def ecdsa_raw_verify(msghash, vrs, pub): - v, r, s = vrs - if not (27 <= v <= 34): - return False - - w = inv(s, N) - z = hash_to_int(msghash) - - u1, u2 = z * w % N, r * w % N - x, y = fast_add(fast_multiply(G, u1), fast_multiply(decode_pubkey(pub), u2)) - return bool(r == x and (r % N) and (s % N)) - - -# For BitcoinCore, (msg = addr or msg = "") be default -def ecdsa_verify_addr(msg, sig, addr): - assert is_address(addr) - Q = ecdsa_recover(msg, sig) - magic = get_version_byte(addr) - return (addr == pubtoaddr(Q, int(magic))) or (addr == pubtoaddr(compress(Q), int(magic))) - - -def ecdsa_verify(msg, sig, pub): - if is_address(pub): - return ecdsa_verify_addr(msg, sig, pub) - return ecdsa_raw_verify(electrum_sig_hash(msg), decode_sig(sig), pub) - - -def ecdsa_raw_recover(msghash, vrs): - v, r, s = vrs - if not (27 <= v <= 34): - raise ValueError("%d must in range 27-31" % v) - x = r - xcubedaxb = (x*x*x+A*x+B) % P - beta = pow(xcubedaxb, (P+1)//4, P) - y = beta if v % 2 ^ beta % 2 else (P - beta) - # If xcubedaxb is not a quadratic residue, then r cannot be the x coord - # for a point on the curve, and so the sig is invalid - if (xcubedaxb - y*y) % P != 0 or not (r % N) or not (s % N): - return False - z = hash_to_int(msghash) - Gz = jacobian_multiply((Gx, Gy, 1), (N - z) % N) - XY = jacobian_multiply((x, y, 1), s) - Qr = jacobian_add(Gz, XY) - Q = jacobian_multiply(Qr, inv(r, N)) - Q = from_jacobian(Q) - - # if ecdsa_raw_verify(msghash, vrs, Q): - return Q - # return False - - -def ecdsa_recover(msg, sig): - v,r,s = decode_sig(sig) - Q = ecdsa_raw_recover(electrum_sig_hash(msg), (v,r,s)) - return encode_pubkey(Q, 'hex_compressed') if v >= 31 else encode_pubkey(Q, 'hex') - -''' - - # add/subtract def add(p1, p2): if is_privkey(p1): From caaa2e6b3ce82ee1edef72adbd1471e278bddd00 Mon Sep 17 00:00:00 2001 From: sea212 Date: Wed, 5 Sep 2018 01:59:31 +0200 Subject: [PATCH 26/27] style --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index 3deb5fbf..ba455e96 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -12,11 +12,11 @@ Account, ) from eth_account.hdaccount.deterministic import ( - PRIVATE, bip32_ckd, bip32_deserialize, bip32_master_key, bip32_privtopub, + PRIVATE, ) from eth_account.hdaccount.mnemonic import ( entropy_to_words, From 21b3f92594fc639b04f1a66c0e323774de7c44f3 Mon Sep 17 00:00:00 2001 From: sea212 Date: Wed, 5 Sep 2018 02:02:35 +0200 Subject: [PATCH 27/27] style --- eth_account/hdaccount.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth_account/hdaccount.py b/eth_account/hdaccount.py index ba455e96..3deb5fbf 100644 --- a/eth_account/hdaccount.py +++ b/eth_account/hdaccount.py @@ -12,11 +12,11 @@ Account, ) from eth_account.hdaccount.deterministic import ( + PRIVATE, bip32_ckd, bip32_deserialize, bip32_master_key, bip32_privtopub, - PRIVATE, ) from eth_account.hdaccount.mnemonic import ( entropy_to_words,