1import hashlib 2 3from .subpaths import subpaths_for_path_range 4 5from pycoin.encoding.bytes32 import from_bytes_32, to_bytes_32 6from pycoin.encoding.hash import double_sha256 7from pycoin.encoding.hexbytes import b2h 8from pycoin.key.Key import Key 9 10 11def initial_key_to_master_key(initial_key): 12 """ 13 initial_key: 14 a hex string of length 32 15 """ 16 b = initial_key.encode("utf8") 17 orig_input = b 18 for i in range(100000): 19 b = hashlib.sha256(b + orig_input).digest() 20 return from_bytes_32(b) 21 22 23class ElectrumWallet(Key): 24 def __init__(self, initial_key=None, master_private_key=None, public_pair=None, master_public_key=None): 25 if [initial_key, public_pair, master_private_key, master_public_key].count(None) != 3: 26 raise ValueError( 27 "exactly one of initial_key, master_private_key, master_public_key must be non-None") 28 self._initial_key = initial_key 29 30 if initial_key is not None: 31 master_private_key = initial_key_to_master_key(initial_key) 32 if master_public_key: 33 public_pair = tuple(from_bytes_32(master_public_key[idx:idx+32]) for idx in (0, 32)) 34 super(ElectrumWallet, self).__init__( 35 secret_exponent=master_private_key, public_pair=public_pair, is_compressed=False) 36 37 @classmethod 38 def deserialize(class_, blob): 39 if len(blob) == 32: 40 return class_(master_private_key=from_bytes_32(blob)) 41 if len(blob) == 64: 42 return class_(master_public_key=blob) 43 44 def serialize(self): 45 if self._secret_exponent: 46 return to_bytes_32(self._secret_exponent) 47 return self.master_public_key() 48 49 def secret_exponent(self): 50 if self._secret_exponent is None and self._initial_key: 51 self._secret_exponent = initial_key_to_master_key(b2h(self._initial_key)) 52 return self._secret_exponent 53 54 def master_private_key(self): 55 return self.secret_exponent() 56 57 def master_public_key(self): 58 return self.sec()[1:] 59 60 def public_copy(self): 61 if self.secret_exponent() is None: 62 return self 63 return self.__class__(public_pair=self.public_pair()) 64 65 def subkey_for_path(self, path): 66 return self.subkey(path) 67 68 def subkey(self, path): 69 """ 70 path: 71 of the form "K" where K is an integer index, or "K/N" where N is usually 72 a 0 (deposit address) or 1 (change address) 73 """ 74 t = path.split("/") 75 if len(t) == 2: 76 n, for_change = t 77 else: 78 n, = t 79 for_change = 0 80 b = (str(n) + ':' + str(for_change) + ':').encode("utf8") + self.master_public_key() 81 offset = from_bytes_32(double_sha256(b)) 82 if self.secret_exponent(): 83 return self.__class__( 84 master_private_key=((self.master_private_key() + offset) % self._generator.order()) 85 ) 86 p1 = offset * self._generator 87 x, y = self.public_pair() 88 p2 = self._generator.Point(x, y) 89 p = p1 + p2 90 return self.__class__(public_pair=p) 91 92 def subkeys(self, path): 93 """ 94 A generalized form that can return multiple subkeys. 95 """ 96 for _ in subpaths_for_path_range(path, hardening_chars="'pH"): 97 yield self.subkey(_) 98 99 def __repr__(self): 100 return "Electrum<E:%s>" % b2h(self.master_public_key()) 101