128 lines
4.0 KiB
Python
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)
|
|
|