cryptoKeyTools/bip32.py

128 lines
4.0 KiB
Python

# BIP32
from hashlib import sha512
import hmac
from util import *
import base58
import ecdsa
import keys
# 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
# 02fac398 - dogecoin prv
PRV_VER = bytes.fromhex( '02fac398' if DOGE_MODE else '0488ADE4' )
# 0488B21E - bitcoin pub
# 02facafd - dogecoin pub
PUB_VER = bytes.fromhex( '02facafd' if DOGE_MODE else '0488B21E' )
# serialize an extended private key (key, chain)
# parfp is the 4byte parent fingerprint
# default parameters assume master key
def serializeExtPrvKey( key, depth=0, parfp=b'\x00\x00\x00\x00', child=0 ):
return base58.encodeCheck( PRV_VER, intBytes( depth, 1 ) +
parfp + intBytes( child, 4 ) + key[1] + b'\x00' + key[0] )
# serialize an extended public key (key, chain)
# key is compressed public point bytes
# parfp is the 4byte parent fingerprint
# default parameters assume master key
def serializeExtPubKey( key, depth=0, parfp=b'\x00\x00\x00\x00', child=0 ):
return base58.encodeCheck( PUB_VER, intBytes( depth, 1 ) +
parfp + intBytes( child, 4 ) + key[1] + key[0] )
# public parent -> public child.
# 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( pubkc, i ):
if i >= (2 ** 31): raise Exception( 'Hardened path invalid with public key.' )
pp = ecdsa.N
while pp >= ecdsa.N:
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( pubkc[0] ) )
k = ecdsa.compressPoint( kp ) # new compressed pubkey
return (k, c)
# private parent -> private child.
# prvkc is (key, chain) where key is prvkey bytes
# returns (key, chain) of new key, key being prvkey bytes too
def ckdPrv( prvkc, i ):
k = 0
pp = ecdsa.N
while k == 0 or pp >= ecdsa.N:
if i >= (2 ** 31):
dat = b'\x00' + prvkc[0] + intBytes( i, 4 )
else:
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( prvkc[0].hex(), 16 )) % ecdsa.N
return (intBytes( k, 32 ), c)