1010)
1111
1212
13+ class PrivateDerivationError (ValueError ):
14+ """
15+ Tried to use a derivation requiring private keys, without private keys.
16+ """
17+ pass
18+
19+
20+ class InvalidInputError (ValueError ):
21+ def __init__ (self , message ):
22+ self .message = message
23+
24+
1325class BIP32 :
1426 def __init__ (self , chaincode , privkey = None , pubkey = None , fingerprint = None ,
1527 depth = 0 , index = 0 , network = "main" ):
@@ -30,14 +42,21 @@ def __init__(self, chaincode, privkey=None, pubkey=None, fingerprint=None,
3042 need this for serialization.
3143 :param network: Either "main" or "test".
3244 """
33- assert isinstance (chaincode , bytes )
34- assert privkey is not None or pubkey is not None
45+ if network not in ["main" , "test" ]:
46+ raise InvalidInputError ("'network' must be one of 'main' or 'test'" )
47+ if not isinstance (chaincode , bytes ):
48+ raise InvalidInputError ("'chaincode' must be bytes" )
49+ if privkey is None and pubkey is None :
50+ raise InvalidInputError ("Need at least a 'pubkey' or a 'privkey'" )
3551 if privkey is not None :
36- assert isinstance (privkey , bytes )
52+ if not isinstance (privkey , bytes ):
53+ raise InvalidInputError ("'privkey' must be bytes" )
3754 if pubkey is not None :
38- assert isinstance (pubkey , bytes )
55+ if not isinstance (pubkey , bytes ):
56+ raise InvalidInputError ("'pubkey' must be bytes" )
3957 else :
4058 pubkey = _privkey_to_pubkey (privkey )
59+
4160 self .master_chaincode = chaincode
4261 self .master_privkey = privkey
4362 self .master_pubkey = pubkey
@@ -53,8 +72,12 @@ def get_extended_privkey_from_path(self, path):
5372 m/x/x'/x notation. (e.g. m/0'/1/2'/2 or m/0H/1/2H/2).
5473 :return: chaincode (bytes), privkey (bytes)
5574 """
75+ if self .master_privkey is None :
76+ raise PrivateDerivationError
77+
5678 if isinstance (path , str ):
5779 path = _deriv_path_str_to_list (path )
80+
5881 chaincode , privkey = self .master_chaincode , self .master_privkey
5982 for index in path :
6083 if index & HARDENED_INDEX :
@@ -63,6 +86,7 @@ def get_extended_privkey_from_path(self, path):
6386 else :
6487 privkey , chaincode = \
6588 _derive_unhardened_private_child (privkey , chaincode , index )
89+
6690 return chaincode , privkey
6791
6892 def get_privkey_from_path (self , path ):
@@ -72,6 +96,9 @@ def get_privkey_from_path(self, path):
7296 m/x/x'/x notation. (e.g. m/0'/1/2'/2 or m/0H/1/2H/2).
7397 :return: privkey (bytes)
7498 """
99+ if self .master_privkey is None :
100+ raise PrivateDerivationError
101+
75102 return self .get_extended_privkey_from_path (path )[1 ]
76103
77104 def get_extended_pubkey_from_path (self , path ):
@@ -83,6 +110,10 @@ def get_extended_pubkey_from_path(self, path):
83110 """
84111 if isinstance (path , str ):
85112 path = _deriv_path_str_to_list (path )
113+
114+ if _hardened_index_in_path (path ) and self .master_privkey is None :
115+ raise PrivateDerivationError
116+
86117 chaincode , key = self .master_chaincode , self .master_privkey
87118 pubkey = self .master_pubkey
88119 # We'll need the private key at some point anyway, so let's derive
@@ -102,6 +133,7 @@ def get_extended_pubkey_from_path(self, path):
102133 for index in path :
103134 pubkey , chaincode = \
104135 _derive_public_child (pubkey , chaincode , index )
136+
105137 return chaincode , pubkey
106138
107139 def get_pubkey_from_path (self , path ):
@@ -120,8 +152,12 @@ def get_xpriv_from_path(self, path):
120152 m/x/x'/x notation. (e.g. m/0'/1/2'/2 or m/0H/1/2H/2).
121153 :return: The encoded extended pubkey as str.
122154 """
155+ if self .master_privkey is None :
156+ raise PrivateDerivationError
157+
123158 if isinstance (path , str ):
124159 path = _deriv_path_str_to_list (path )
160+
125161 if len (path ) == 0 :
126162 return self .get_master_xpriv ()
127163 elif len (path ) == 1 :
@@ -133,6 +169,7 @@ def get_xpriv_from_path(self, path):
133169 parent_pubkey ,
134170 path [- 1 ], chaincode ,
135171 self .network )
172+
136173 return base58 .b58encode_check (extended_key ).decode ()
137174
138175 def get_xpub_from_path (self , path ):
@@ -144,6 +181,10 @@ def get_xpub_from_path(self, path):
144181 """
145182 if isinstance (path , str ):
146183 path = _deriv_path_str_to_list (path )
184+
185+ if _hardened_index_in_path (path ) and self .master_privkey is None :
186+ raise PrivateDerivationError
187+
147188 if len (path ) == 0 :
148189 return self .get_master_xpub ()
149190 elif len (path ) == 1 :
@@ -155,10 +196,13 @@ def get_xpub_from_path(self, path):
155196 parent_pubkey ,
156197 path [- 1 ], chaincode ,
157198 self .network )
199+
158200 return base58 .b58encode_check (extended_key ).decode ()
159201
160202 def get_master_xpriv (self ):
161203 """Get the encoded extended private key of the master private key"""
204+ if self .master_privkey is None :
205+ raise PrivateDerivationError
162206 extended_key = _serialize_extended_key (self .master_privkey , self .depth ,
163207 self .parent_fingerprint ,
164208 self .index ,
@@ -181,6 +225,9 @@ def from_xpriv(cls, xpriv):
181225
182226 :param xpriv: (str) The encoded serialized extended private key.
183227 """
228+ if not isinstance (xpriv , str ):
229+ raise InvalidInputError ("'xpriv' must be a string" )
230+
184231 extended_key = base58 .b58decode_check (xpriv )
185232 (network , depth , fingerprint ,
186233 index , chaincode , key ) = _unserialize_extended_key (extended_key )
@@ -194,6 +241,9 @@ def from_xpub(cls, xpub):
194241
195242 :param xpub: (str) The encoded serialized extended public key.
196243 """
244+ if not isinstance (xpub , str ):
245+ raise InvalidInputError ("'xpub' must be a string" )
246+
197247 extended_key = base58 .b58decode_check (xpub )
198248 (network , depth , fingerprint ,
199249 index , chaincode , key ) = _unserialize_extended_key (extended_key )
0 commit comments