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:
Josh K 2019-07-27 16:28:43 -04:00
parent b7f50679c7
commit a610dfc756
Signed by: slipyx
GPG Key ID: 09CEED554B4B1172
2 changed files with 84 additions and 37 deletions

View File

@ -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
View File

@ -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()