3636 except :
3737 print ("Json or simplejson package is needed" )
3838
39+ import hmac
3940import getpass
4041import logging
4142import struct
9596prekeys = ["308201130201010420" .decode ('hex' ), "308201120201010420" .decode ('hex' )]
9697postkeys = ["a081a530" .decode ('hex' ), "81a530" .decode ('hex' )]
9798
99+ KeyInfo = collections .namedtuple ('KeyInfo' , 'secret private_key public_key addr wif' )
100+
98101def plural (a ):
99102 if a >= 2 :return 's'
100103 return ''
@@ -2622,6 +2625,9 @@ def parse_private_key(sec, force_compressed=None):
26222625 return (pkey , compressed )
26232626
26242627def keyinfo (sec , network = None , print_info = False , force_compressed = None ):
2628+ if sec .__class__ == Xpriv :
2629+ assert sec .ktype == 0
2630+ return keyinfo (sec .key .encode ('hex' ), network , print_info , True )
26252631 network = network or network_bitcoin
26262632 (pkey , compressed ) = parse_private_key (sec , force_compressed )
26272633 if not pkey :
@@ -2632,6 +2638,7 @@ def keyinfo(sec, network=None, print_info=False, force_compressed=None):
26322638 public_key = GetPubKey (pkey , compressed )
26332639 addr = public_key_to_bc_address (public_key , network .p2pkh_prefix )
26342640 ser_public_key = (b'%02d%.64x' % (4 if not compressed else 2 + (pkey .pubkey .point .y ()& 1 ), pkey .pubkey .point .x ()) + (b'%.64x' % pkey .pubkey .point .y ())* int (not compressed )).decode ('hex' )
2641+ wif = SecretToASecret (secret , compressed ) if network .wif_prefix else None
26352642
26362643 if print_info :
26372644 print ("Network: %s" % network .name )
@@ -2649,7 +2656,7 @@ def keyinfo(sec, network=None, print_info=False, force_compressed=None):
26492656 else :
26502657 print ("P2WPKH unavailable: unknown network SegWit HRP" )
26512658 if network .wif_prefix != None :
2652- print ("Privkey: %s" % ( SecretToASecret ( secret , compressed )) )
2659+ print ("Privkey: %s" % wif )
26532660 else :
26542661 print ("Privkey unavailable: unknown network WIF prefix" )
26552662 print ("Hexprivkey: %s" % (secret .encode ('hex' )))
@@ -2660,10 +2667,14 @@ def keyinfo(sec, network=None, print_info=False, force_compressed=None):
26602667 if int (secret .encode ('hex' ), 16 )> _r :
26612668 print ('/!\\ Beware, 0x%s is equivalent to 0x%.33x' % (secret .encode ('hex' ), int (secret .encode ('hex' ), 16 )- _r ))
26622669
2663- return (secret , private_key , public_key , addr )
2670+ return KeyInfo (secret , private_key , public_key , addr , wif )
26642671
26652672def importprivkey (db , sec , label , reserve , verbose = True ):
2666- (secret , private_key , public_key , addr ) = keyinfo (sec , network , verbose )
2673+ k = keyinfo (sec , network , verbose )
2674+ secret = k .secret
2675+ private_key = k .private_key
2676+ public_key = k .public_key
2677+ addr = k .addr
26672678
26682679 global crypter , passphrase , json_db
26692680 crypted = False
@@ -3228,9 +3239,93 @@ def whitepaper():
32283239 f .write (content )
32293240 print ("Wrote the Bitcoin whitepaper to %s.pdf" % filename )
32303241
3242+ class Xpriv (collections .namedtuple ('Xpriv' , 'version depth prt_fpr childnr cc ktype key' )):
3243+ xpriv_fmt = '>IB4sI32sB32s'
3244+ def __init__ (self , * a , ** kw ):
3245+ super (Xpriv , self ).__init__ (* a , ** kw )
3246+ self .fullpath = 'm'
3247+ @classmethod
3248+ def xpriv_version_bytes (cls ):return 0x0488ADE4
3249+ @classmethod
3250+ def xpub_version_bytes (cls ):return 0x0488B21E
3251+ @classmethod
3252+ def from_seed (cls , s ):
3253+ I = hmac .new (b'Bitcoin seed' , s , digestmod = hashlib .sha512 ).digest ()
3254+ mk , cc = I [:32 ], I [32 :]
3255+ return cls (cls .xpriv_version_bytes (), 0 , '\x00 ' * 4 , 0 , cc , 0 , mk )
3256+ def clone (self ):
3257+ return self .__class__ .b58decode (self .b58encode ())
3258+ def b58encode (self ):
3259+ return EncodeBase58Check (struct .pack (self .xpriv_fmt , * self ._asdict ().values ()))
3260+ def xpub (self ):
3261+ pubk = keyinfo (self , None , False , True ).public_key
3262+ xpub_content = self .clone ()._replace (version = self .xpub_version_bytes (), ktype = ord (pubk [0 ]), key = pubk [1 :])
3263+ return EncodeBase58Check (struct .pack (self .xpriv_fmt , * xpub_content ))
3264+ @classmethod
3265+ def b58decode (cls , b58xpriv ):
3266+ return cls (* struct .unpack (cls .xpriv_fmt , DecodeBase58Check (b58xpriv )))
3267+ def multi_ckd_xpriv (self , str_path ):
3268+ str_path = str_path .lstrip ('m/' )
3269+ path_split = []
3270+ for j in str_path .split ('/' ):
3271+ if not j :continue
3272+ hardened = 0
3273+ if j .endswith ("'" ) or j .lower ().endswith ("H" ):
3274+ hardened = 0x80000000
3275+ j = j [:- 1 ]
3276+ try :
3277+ path_split .append ([int (j )+ hardened ])
3278+ except :
3279+ a , b = map (int , j .split ('-' ))
3280+ path_split .append (list (range (a + hardened , b + 1 + hardened )))
3281+ rev_path_split = path_split [::- 1 ]
3282+ xprivs = [self ]
3283+ while rev_path_split :
3284+ children_nrs = rev_path_split .pop ()
3285+ xprivs = [parent .ckd_xpriv (child_nr ) for parent in xprivs for child_nr in children_nrs ]
3286+ return xprivs
3287+ def set_fullpath (self , base , x ):
3288+ self .fullpath = base + '/' + ("%d'" % (x - 0x80000000 ) if x >= 0x80000000 else "%d" % x )
3289+ return self
3290+ def ckd_xpriv (self , * indexes ):
3291+ if indexes .__class__ != tuple :
3292+ indexes = [indexes ]
3293+ i = indexes [0 ]
3294+ if i < 0 :
3295+ i = 0x80000000 - i
3296+ assert self .ktype == 0
3297+ par_pubk = keyinfo (self , None , False , True ).public_key
3298+ seri = struct .pack ('>I' ,i )
3299+ if i >= 0x80000000 :
3300+ I = hmac .new (self .cc , '\x00 ' + self .key + seri , digestmod = hashlib .sha512 ).digest ()
3301+ else :
3302+ I = hmac .new (self .cc , par_pubk + seri , digestmod = hashlib .sha512 ).digest ()
3303+ il , ir = I [:32 ], I [32 :]
3304+ pk = hex ((int (il .encode ('hex' ), 16 ) + int (self .key .encode ('hex' ), 16 ))% _r )[2 :].replace ('L' , '' ).zfill (64 )
3305+ child = self .__class__ (self .version , self .depth + 1 , hash_160 (par_pubk )[:4 ], i , ir , 0 , pk .decode ('hex' )).set_fullpath (self .fullpath , i )
3306+ if len (indexes )>= 2 :
3307+ return child .ckd_xpriv (* indexes [1 :])
3308+ return child
3309+ def hprivcontent (self ):
3310+ return DecodeBase58Check (self .b58encode ()).encode ('hex' )
3311+ def hpubcontent (self ):
3312+ return DecodeBase58Check (self .xpub ()).encode ('hex' )
3313+
3314+ def dump_bip32_privkeys (xpriv , paths , format ):
3315+ dump_key = lambda x :x .wif
3316+ if format == 'addr' :dump_key = lambda x :x .addr
3317+ for child in Xpriv .b58decode (xpriv ).multi_ckd_xpriv (paths ):
3318+ print ('%s: %s' % (child .fullpath , dump_key (keyinfo (child ))))
3319+
32313320if __name__ == '__main__' :
32323321 parser = OptionParser (usage = "%prog [options]" , version = "%prog 1.1" )
32333322
3323+ parser .add_option ("--dump_bip32" , nargs = 2 ,
3324+ help = "dump the keys from a xpriv and a path, usage: --dump_bip32 xprv9s21ZrQH143K m/0H/1-2/2H/2-4" )
3325+
3326+ parser .add_option ("--bip32_format" ,
3327+ help = "format of dumped bip32 keys" )
3328+
32343329 parser .add_option ("--passphrase" , dest = "passphrase" ,
32353330 help = "passphrase for the encrypted wallet" )
32363331
@@ -3324,6 +3419,11 @@ def whitepaper():
33243419# if options.forcerun is None:
33253420# exit(0)
33263421
3422+ if options .dump_bip32 :
3423+ print ("Warning: single quotes (') may be parsed by your terminal, please use \" H\" for hardened keys" )
3424+ dump_bip32_privkeys (* options .dump_bip32 , format = options .bip32_format )
3425+ exit ()
3426+
33273427 if options .whitepaper :
33283428 whitepaper ()
33293429 exit ()
0 commit comments