16
16
from mnemonic import Mnemonic
17
17
from nacl import bindings
18
18
19
+ from pycardano .logging import logger
20
+
19
21
__all__ = ["BIP32ED25519PrivateKey" , "BIP32ED25519PublicKey" , "HDWallet" ]
20
22
21
23
24
+ SUPPORTED_MNEMONIC_LANGS = {
25
+ "english" ,
26
+ "french" ,
27
+ "italian" ,
28
+ "japanese" ,
29
+ "chinese_simplified" ,
30
+ "chinese_traditional" ,
31
+ "korean" ,
32
+ "spanish" ,
33
+ }
34
+
35
+
22
36
class BIP32ED25519PrivateKey :
23
37
def __init__ (self , private_key : bytes , chain_code : bytes ):
24
38
self .private_key = private_key
@@ -109,6 +123,9 @@ def from_seed(
109
123
110
124
Args:
111
125
seed: Master key of 96 bytes from seed hex string.
126
+ entropy: Entropy hex string, default to ``None``.
127
+ passphrase: Mnemonic passphrase or password, default to ``None``.
128
+ mnemonic: Mnemonic words, default to ``None``.
112
129
113
130
Returns:
114
131
HDWallet -- Hierarchical Deterministic Wallet instance.
@@ -152,28 +169,18 @@ def from_mnemonic(cls, mnemonic: str, passphrase: str = "") -> HDWallet:
152
169
raise ValueError ("Invalid mnemonic words." )
153
170
154
171
mnemonic = unicodedata .normalize ("NFKD" , mnemonic )
155
- passphrase = str (passphrase ) if passphrase else ""
156
172
entropy = Mnemonic (language = "english" ).to_entropy (words = mnemonic )
157
-
158
- seed = bytearray (
159
- hashlib .pbkdf2_hmac (
160
- "sha512" ,
161
- password = passphrase .encode (),
162
- salt = entropy ,
163
- iterations = 4096 ,
164
- dklen = 96 ,
165
- )
166
- )
173
+ seed = cls ._generate_seed (passphrase , entropy )
167
174
168
175
return cls .from_seed (
169
176
seed = hexlify (seed ).decode (),
170
177
mnemonic = mnemonic ,
171
- entropy = entropy ,
178
+ entropy = hexlify ( entropy ). decode ( "utf-8" ) ,
172
179
passphrase = passphrase ,
173
180
)
174
181
175
182
@classmethod
176
- def from_entropy (cls , entropy : str , passphrase : str = None ) -> HDWallet :
183
+ def from_entropy (cls , entropy : str , passphrase : str = "" ) -> HDWallet :
177
184
"""
178
185
Create master key and HDWallet from Mnemonic words.
179
186
@@ -188,12 +195,20 @@ def from_entropy(cls, entropy: str, passphrase: str = None) -> HDWallet:
188
195
if not cls .is_entropy (entropy ):
189
196
raise ValueError ("Invalid entropy" )
190
197
191
- seed = bytearray (
198
+ seed = cls ._generate_seed (passphrase , bytearray .fromhex (entropy ))
199
+ return cls .from_seed (seed = hexlify (seed ).decode (), entropy = entropy )
200
+
201
+ @classmethod
202
+ def _generate_seed (cls , passphrase : str , entropy : bytearray ) -> bytearray :
203
+ return bytearray (
192
204
hashlib .pbkdf2_hmac (
193
- "sha512" , password = passphrase , salt = entropy , iterations = 4096 , dklen = 96
205
+ "sha512" ,
206
+ password = passphrase .encode (),
207
+ salt = entropy ,
208
+ iterations = 4096 ,
209
+ dklen = 96 ,
194
210
)
195
211
)
196
- return cls .from_seed (seed = hexlify (seed ).decode (), entropy = entropy )
197
212
198
213
@classmethod
199
214
def _tweak_bits (cls , seed : bytearray ) -> bytes :
@@ -264,28 +279,26 @@ def derive_from_path(self, path: str, private: bool = True) -> HDWallet:
264
279
)
265
280
266
281
derived_hdwallet = self ._copy_hdwallet ()
267
-
268
282
for index in path .lstrip ("m/" ).split ("/" ):
269
283
if index .endswith ("'" ):
270
- derived_hdwallet = self . derive_from_index (
271
- derived_hdwallet , int (index [:- 1 ]), private = private , hardened = True
284
+ derived_hdwallet = derived_hdwallet . derive (
285
+ int (index [:- 1 ]), private = private , hardened = True
272
286
)
273
287
else :
274
- derived_hdwallet = self . derive_from_index (
275
- derived_hdwallet , int (index ), private = private , hardened = False
288
+ derived_hdwallet = derived_hdwallet . derive (
289
+ int (index ), private = private , hardened = False
276
290
)
277
291
278
292
return derived_hdwallet
279
293
280
- def derive_from_index (
294
+ def derive (
281
295
self ,
282
- parent_wallet : HDWallet ,
283
296
index : int ,
284
297
private : bool = True ,
285
298
hardened : bool = False ,
286
299
) -> HDWallet :
287
300
"""
288
- Derive keys from index.
301
+ Returns a new HDWallet derived from given index.
289
302
290
303
Args:
291
304
index: Derivation index.
@@ -298,12 +311,12 @@ def derive_from_index(
298
311
Examples:
299
312
>>> mnemonic_words = "test walk nut penalty hip pave soap entry language right filter choice"
300
313
>>> hdwallet = HDWallet.from_mnemonic(mnemonic_words)
301
- >>> hdwallet_l1 = hdwallet.derive_from_index(parent_wallet=hdwallet, index=1852, hardened=True)
302
- >>> hdwallet_l2 = hdwallet.derive_from_index(parent_wallet=hdwallet_l1, index=1815, hardened=True)
303
- >>> hdwallet_l3 = hdwallet.derive_from_index(parent_wallet=hdwallet_l2, index=0, hardened=True)
304
- >>> hdwallet_l4 = hdwallet.derive_from_index(parent_wallet=hdwallet_l3, index=0)
305
- >>> hdwallet_l5 = hdwallet.derive_from_index(parent_wallet=hdwallet_l4, index=0)
306
- >>> hdwallet_l5 .public_key.hex()
314
+ >>> hdwallet = hdwallet.derive( index=1852, hardened=True)
315
+ >>> hdwallet = hdwallet.derive( index=1815, hardened=True)
316
+ >>> hdwallet = hdwallet.derive( index=0, hardened=True)
317
+ >>> hdwallet = hdwallet.derive( index=0)
318
+ >>> hdwallet = hdwallet.derive( index=0)
319
+ >>> hdwallet .public_key.hex()
307
320
'73fea80d424276ad0978d4fe5310e8bc2d485f5f6bb3bf87612989f112ad5a7d'
308
321
"""
309
322
@@ -319,19 +332,19 @@ def derive_from_index(
319
332
# derive private child key
320
333
if private :
321
334
node = (
322
- parent_wallet ._xprivate_key [:32 ],
323
- parent_wallet ._xprivate_key [32 :],
324
- parent_wallet ._public_key ,
325
- parent_wallet ._chain_code ,
326
- parent_wallet ._path ,
335
+ self ._xprivate_key [:32 ],
336
+ self ._xprivate_key [32 :],
337
+ self ._public_key ,
338
+ self ._chain_code ,
339
+ self ._path ,
327
340
)
328
341
derived_hdwallet = self ._derive_private_child_key_by_index (node , index )
329
342
# derive public child key
330
343
else :
331
344
node = (
332
- parent_wallet ._public_key ,
333
- parent_wallet ._chain_code ,
334
- parent_wallet ._path ,
345
+ self ._public_key ,
346
+ self ._chain_code ,
347
+ self ._path ,
335
348
)
336
349
derived_hdwallet = self ._derive_public_child_key_by_index (node , index )
337
350
@@ -416,7 +429,13 @@ def _derive_private_child_key_by_index(
416
429
path += "/" + str (index )
417
430
418
431
derived_hdwallet = HDWallet (
419
- xprivate_key = kL + kR , public_key = A , chain_code = c , path = path
432
+ xprivate_key = kL + kR ,
433
+ public_key = A ,
434
+ chain_code = c ,
435
+ path = path ,
436
+ root_xprivate_key = self .root_xprivate_key ,
437
+ root_public_key = self .root_public_key ,
438
+ root_chain_code = self .root_chain_code ,
420
439
)
421
440
422
441
return derived_hdwallet
@@ -469,7 +488,14 @@ def _derive_public_child_key_by_index(
469
488
# compute path
470
489
path += "/" + str (index )
471
490
472
- derived_hdwallet = HDWallet (public_key = A , chain_code = c , path = path )
491
+ derived_hdwallet = HDWallet (
492
+ public_key = A ,
493
+ chain_code = c ,
494
+ path = path ,
495
+ root_xprivate_key = self .root_xprivate_key ,
496
+ root_public_key = self .root_public_key ,
497
+ root_chain_code = self .root_chain_code ,
498
+ )
473
499
474
500
return derived_hdwallet
475
501
@@ -510,16 +536,7 @@ def generate_mnemonic(language: str = "english", strength: int = 256) -> str:
510
536
mnemonic (str): mnemonic words.
511
537
"""
512
538
513
- if language and language not in [
514
- "english" ,
515
- "french" ,
516
- "italian" ,
517
- "japanese" ,
518
- "chinese_simplified" ,
519
- "chinese_traditional" ,
520
- "korean" ,
521
- "spanish" ,
522
- ]:
539
+ if language and language not in SUPPORTED_MNEMONIC_LANGS :
523
540
raise ValueError (
524
541
"invalid language, use only this options english, french, "
525
542
"italian, spanish, chinese_simplified, chinese_traditional, japanese or korean languages."
@@ -545,42 +562,22 @@ def is_mnemonic(mnemonic: str, language: Optional[str] = None) -> bool:
545
562
bool. Whether the input mnemonic words is valid.
546
563
"""
547
564
548
- if language and language not in [
549
- "english" ,
550
- "french" ,
551
- "italian" ,
552
- "japanese" ,
553
- "chinese_simplified" ,
554
- "chinese_traditional" ,
555
- "korean" ,
556
- "spanish" ,
557
- ]:
565
+ if language and language not in SUPPORTED_MNEMONIC_LANGS :
558
566
raise ValueError (
559
567
"invalid language, use only this options english, french, "
560
568
"italian, spanish, chinese_simplified, chinese_traditional, japanese or korean languages."
561
569
)
562
570
try :
563
571
mnemonic = unicodedata .normalize ("NFKD" , mnemonic )
564
- if language is None :
565
- for _language in [
566
- "english" ,
567
- "french" ,
568
- "italian" ,
569
- "chinese_simplified" ,
570
- "chinese_traditional" ,
571
- "japanese" ,
572
- "korean" ,
573
- "spanish" ,
574
- ]:
575
- valid = False
576
- if Mnemonic (language = _language ).check (mnemonic = mnemonic ) is True :
577
- valid = True
578
- break
579
- return valid
580
- else :
572
+ if language :
581
573
return Mnemonic (language = language ).check (mnemonic = mnemonic )
574
+
575
+ for _language in SUPPORTED_MNEMONIC_LANGS :
576
+ if Mnemonic (language = _language ).check (mnemonic = mnemonic ) is True :
577
+ return True
578
+ return False
582
579
except ValueError :
583
- print (
580
+ logger . warning (
584
581
"The input mnemonic words are not valid. Words should be in string format seperated by space."
585
582
)
586
583
@@ -599,4 +596,5 @@ def is_entropy(entropy: str) -> bool:
599
596
try :
600
597
return len (unhexlify (entropy )) in [16 , 20 , 24 , 28 , 32 ]
601
598
except ValueError :
602
- print ("The input entropy is not valid." )
599
+ logger .warning ("The input entropy is not valid." )
600
+ return False
0 commit comments