Added ExtKey classes for wrapping some functionality of bip32 extended keys. Added ExtPrvKey methods for getting derived keys, g etting child keys, and constructing from master seed. Also some relevant main.py tests. ExtPubKey class WIP.
This commit is contained in:
parent
b7f50679c7
commit
a610dfc756
87
bip32.py
87
bip32.py
|
@ -7,12 +7,67 @@ import base58
|
|||
import ecdsa
|
||||
import keys
|
||||
|
||||
# get master (key, chain) of seed bytes
|
||||
def getMaster( seed ):
|
||||
l = hmac.new( b'Bitcoin seed', seed, sha512 ).digest()
|
||||
m = l[:32]
|
||||
c = l[32:]
|
||||
return (m, c)
|
||||
# extended key classes
|
||||
|
||||
# common base class for extended keys
|
||||
class ExtKeyBase:
|
||||
def __init__( self, keychain, depth=0, parfp=b'\x00\x00\x00\x00', child=0 ):
|
||||
""" keychain is (key, chain) bytes, depth is path depth,
|
||||
parfp is parent fingerprint, child is root child index """
|
||||
self.keychain = keychain
|
||||
self.depth = depth
|
||||
self.parfp = parfp
|
||||
self.child = child
|
||||
|
||||
class ExtPrvKey( ExtKeyBase ):
|
||||
# construct a master ExtPrvKey from given seed bytes
|
||||
@classmethod
|
||||
def fromSeed( self, seed ):
|
||||
l = hmac.new( b'Bitcoin seed', seed, sha512 ).digest()
|
||||
m = l[:32]
|
||||
c = l[32:]
|
||||
return ExtPrvKey( (m, c) )
|
||||
|
||||
def serialize( self ):
|
||||
return serializeExtPrvKey(
|
||||
self.keychain, self.depth, self.parfp, self.child )
|
||||
|
||||
# return ext prv key for derived path from self where 'm' = self
|
||||
def getDerivedExtPrvKey( self, path ):
|
||||
# strip trailing slashes
|
||||
while path[-1] == '/': path = path[:-1]
|
||||
|
||||
derivkc = self.keychain # initial "m" keychain
|
||||
parfp = self.parfp # last parent fingerprint
|
||||
dep = self.depth + (len( path.split( '/' ) ) - 1) # initial depth
|
||||
ci = self.child # initial child index
|
||||
|
||||
for c in path.split( '/' ):
|
||||
if c == 'm': continue
|
||||
if c[-1] == "'":
|
||||
ci = (2 ** 31) + int( c[:-1] )
|
||||
else:
|
||||
ci = int( c )
|
||||
|
||||
parfp = keys.getPubKeyHash( keys.getPubKey( derivkc[0] ) )[:4] # parent fingerprint
|
||||
derivkc = ckdPrv( derivkc, ci )
|
||||
|
||||
return ExtPrvKey( derivkc, dep, parfp, ci )
|
||||
|
||||
# get nth child private key of self
|
||||
def getChildPrvKey( self, i ):
|
||||
return ckdPrv( self.keychain, i )[0]
|
||||
|
||||
class ExtPubKey( ExtKeyBase ):
|
||||
# construct from a matching ExtPrvKey
|
||||
@classmethod
|
||||
def fromExtPrvKey( self, extprvkey ):
|
||||
return ExtPubKey( (keys.getPubKey( extprvkey.keychain[0] ),
|
||||
extprvkey.keychain[1]), extprvkey.depth, extprvkey.parfp, extprvkey.child )
|
||||
|
||||
def serialize( self ):
|
||||
return serializeExtPubKey(
|
||||
self.keychain, self.depth, self.parfp, self.child )
|
||||
|
||||
# serialized version byte codes
|
||||
# 0488ADE4 - bitcoin prv
|
||||
|
@ -38,35 +93,35 @@ def serializeExtPubKey( key, depth=0, parfp=b'\x00\x00\x00\x00', child=0 ):
|
|||
parfp + intBytes( child, 4 ) + key[1] + key[0] )
|
||||
|
||||
# public parent -> public child.
|
||||
# pub is (key, chain) where key is the compressed pubkey bytes
|
||||
# pubkc is (key, chain) where key is the compressed pubkey bytes
|
||||
# returns (key, chain) of new key, key being compressed pubkey bytes too
|
||||
def ckdPub( pub, i ):
|
||||
def ckdPub( pubkc, i ):
|
||||
if i >= (2 ** 31): raise Exception( 'Hardened path invalid with public key.' )
|
||||
pp = ecdsa.N
|
||||
while pp >= ecdsa.N:
|
||||
l = hmac.new( pub[1], pub[0] + intBytes( i, 4 ), sha512 ).digest()
|
||||
l = hmac.new( pubkc[1], pubkc[0] + intBytes( i, 4 ), sha512 ).digest()
|
||||
c = l[32:] # new chain
|
||||
pp = int( l[:32].hex(), 16 ) # new pseudo-private key
|
||||
kp = ecdsa.ecAdd( ecdsa.getPoint( pp ), ecdsa.decompressPoint( pub[0] ) )
|
||||
kp = ecdsa.ecAdd( ecdsa.getPoint( pp ), ecdsa.decompressPoint( pubkc[0] ) )
|
||||
k = ecdsa.compressPoint( kp ) # new compressed pubkey
|
||||
|
||||
return (k, c)
|
||||
|
||||
# private parent -> private child.
|
||||
# prv is (key, chain) where key is prvkey bytes
|
||||
# prvkc is (key, chain) where key is prvkey bytes
|
||||
# returns (key, chain) of new key, key being prvkey bytes too
|
||||
def ckdPrv( prv, i ):
|
||||
def ckdPrv( prvkc, i ):
|
||||
k = 0
|
||||
pp = ecdsa.N
|
||||
while k == 0 or pp >= ecdsa.N:
|
||||
if i >= (2 ** 31):
|
||||
dat = b'\x00' + prv[0] + intBytes( i, 4 )
|
||||
dat = b'\x00' + prvkc[0] + intBytes( i, 4 )
|
||||
else:
|
||||
dat = keys.getPubKey( prv[0] ) + intBytes( i, 4 )
|
||||
l = hmac.new( prv[1], dat, sha512 ).digest()
|
||||
dat = keys.getPubKey( prvkc[0] ) + intBytes( i, 4 )
|
||||
l = hmac.new( prvkc[1], dat, sha512 ).digest()
|
||||
c = l[32:] # new chain
|
||||
pp = int( l[:32].hex(), 16 )
|
||||
k = (pp + int( prv[0].hex(), 16 )) % ecdsa.N
|
||||
k = (pp + int( prvkc[0].hex(), 16 )) % ecdsa.N
|
||||
|
||||
return (intBytes( k, 32 ), c)
|
||||
|
||||
|
|
34
main.py
34
main.py
|
@ -18,36 +18,28 @@ if __name__ == '__main__':
|
|||
b39seed = bip39.genSeed( b39phrase )
|
||||
|
||||
# test bip32
|
||||
# master private (key, chain) bytes
|
||||
b32master = bip32.getMaster( bytes.fromhex( '000102030405060708090a0b0c0d0e0f' ) )
|
||||
# master private extended key from seed bytes
|
||||
masterPrv = bip32.ExtPrvKey.fromSeed(
|
||||
bytes.fromhex( '4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be' ) )
|
||||
# serialize master ext prv and pub key
|
||||
b32masterxprv = bip32.serializeExtPrvKey( b32master )
|
||||
b32masterxpub = bip32.serializeExtPubKey( (keys.getPubKey( b32master[0] ), b32master[1]) )
|
||||
masterxprv = masterPrv.serialize()
|
||||
masterxpub = bip32.ExtPubKey.fromExtPrvKey( masterPrv ).serialize()
|
||||
|
||||
print( 'BIP39 phrase, seed\n\t', b39phrase + '\n\t', b39seed.hex() +
|
||||
'\nBIP32 master\n' + f'\t{b32masterxprv}\n\t{b32masterxpub}' )
|
||||
'\nBIP32 master\n' + f'\t{masterxprv}\n\t{masterxpub}' )
|
||||
print()
|
||||
|
||||
derivPath = "m/0'/1"
|
||||
prvMaster = (b32master[0], b32master[1])
|
||||
derivPath = "m/0'"
|
||||
prvDeriv = masterPrv.getDerivedExtPrvKey( derivPath )
|
||||
pubDeriv = bip32.ExtPubKey.fromExtPrvKey( prvDeriv )
|
||||
|
||||
prvDeriv = prvMaster # m
|
||||
prvDeriv = bip32.ckdPrv( prvDeriv, (2**31) + 0 ) # m/0'
|
||||
|
||||
parfp = keys.getPubKeyHash( keys.getPubKey( prvDeriv[0] ) )[:4] # parent fingerprint
|
||||
prvDeriv = bip32.ckdPrv( prvDeriv, 1 ) # m/0'/1
|
||||
|
||||
# serialize path root
|
||||
prvDerivExt = bip32.serializeExtPrvKey(
|
||||
prvDeriv, 2, parfp, 1)# (2**31)+0 )
|
||||
pubDerivExt = bip32.serializeExtPubKey(
|
||||
(keys.getPubKey( prvDeriv[0] ), prvDeriv[1]), 2, parfp, 1)#(2**31)+0 )
|
||||
print( f'derived ext keys:\n\t{prvDerivExt}\n\t{pubDerivExt}' )
|
||||
print( f'derived ext keys:\n\t{prvDeriv.serialize()}\n\t{pubDeriv.serialize()}' )
|
||||
|
||||
for i in range( 5 ):
|
||||
# pubkey - prvkey
|
||||
pk = bip32.ckdPrv( prvDeriv, i )[0]
|
||||
print( '{}/{}: {} - {}'.format( derivPath, i, keys.getPubKey( pk ).hex(), keys.getPrvKeyWIF( pk ) ) )
|
||||
pk = prvDeriv.getChildPrvKey( i )
|
||||
print( '{}/{}: {} - {}'.format( derivPath, i,
|
||||
keys.getPubKey( pk ).hex(), keys.getPrvKeyWIF( pk ) ) )
|
||||
|
||||
"""
|
||||
pp = b'poop' #input( 'Phrase: ' ).encode()
|
||||
|
|
Loading…
Reference in New Issue