diff --git a/ucoinpy/__init__.py b/ucoinpy/__init__.py index 0eb79a469b6c7977fe9734e20b2a5163b63f9472..354362cd909050638e202e6df9b96384bf8fc7c0 100644 --- a/ucoinpy/__init__.py +++ b/ucoinpy/__init__.py @@ -15,3 +15,7 @@ # Authors: # Caner Candan <caner@candan.fr>, http://caner.candan.fr # + +PROTOCOL_VERSION="1" + +MANAGED_API=["BASIC_MERKLED_API"] diff --git a/ucoinpy/documents/__init__.py b/ucoinpy/documents/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d0057d916ed98558dc74d2c1678b35ffb32895f0 100644 --- a/ucoinpy/documents/__init__.py +++ b/ucoinpy/documents/__init__.py @@ -0,0 +1,25 @@ +''' +Created on 3 déc. 2014 + +@author: inso +''' +import base58 +import time +from ..key import Base58Encoder + + +class Document: + def __init__(self, timestamp): + self.timestamp = timestamp + + def ts(self): + return "META:TS:{0}".format(self.timestamp) + + def content(self): + return "" + + def sign(self, key): + return key.sign(self.content(), encoder=Base58Encoder) + + def signed(self, key): + return "{0}\n{1}\n".format(self.content(), self.sign(key)) diff --git a/ucoinpy/documents/block.py b/ucoinpy/documents/block.py index 8b0772d41523b36ce41a5ed456dd414a5af79226..33d1e043f6a247aeab74a169d7921fc88aceddb1 100644 --- a/ucoinpy/documents/block.py +++ b/ucoinpy/documents/block.py @@ -4,13 +4,131 @@ Created on 2 déc. 2014 @author: inso ''' -class Block(object): +from .. import PROTOCOL_VERSION +from . import Document + + +class Block(Document): ''' - classdocs +Version: VERSION +Type: Block +Currency: CURRENCY +Nonce: NONCE +Number: BLOCK_NUMBER +PoWMin: NUMBER_OF_ZEROS +Time: GENERATED_ON +MedianTime: MEDIAN_DATE +UniversalDividend: DIVIDEND_AMOUNT +Issuer: ISSUER_KEY +PreviousHash: PREVIOUS_HASH +PreviousIssuer: PREVIOUS_ISSUER_KEY +Parameters: PARAMETERS +MembersCount: WOT_MEM_COUNT +Identities: +PUBLIC_KEY:SIGNATURE:TIMESTAMP:USER_ID +... +Joiners: +PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID +... +Actives: +PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID +... +Leavers: +PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID +... +Excluded: +PUBLIC_KEY +... +Certifications: +PUBKEY_FROM:PUBKEY_TO:BLOCK_NUMBER:SIGNATURE +... +Transactions: +COMPACT_TRANSACTION +... +BOTTOM_SIGNATURE ''' - - def __init__(self, params): + def __init__(self, currency, noonce, number, powmin, time, + mediantime, ud, issuer, prev_hash, prev_issuer, + parameters, members_count, identities, joiners, + actives, leavers, excluded, certifications, + transactions): ''' Constructor ''' + self.currency = currency + self.noonce = noonce + self.number = number + self.powmin = powmin + self.time = time + self.mediantime = mediantime + self.ud = ud + self.issuer = issuer + self.prev_hash = prev_hash + self.prev_issuer = prev_issuer + self.parameters = parameters + self.members_count = members_count + self.identities = identities + self.joiners = joiners + self.actives = actives + self.leavers = leavers + self.excluded = excluded + self.certifications = certifications + self.transactions = transactions + + def content(self): + doc = """ +Version: {0} +Type: Block +Currency: {1} +Nonce: {2} +Number: {3} +PoWMin: {4} +Time: {5} +MedianTime: {6} +UniversalDividend: {7} +Issuer: {8} +PreviousHash: {9} +PreviousIssuer: {10} +Parameters: {11} +MembersCount: {12} +Identities:""".format(PROTOCOL_VERSION, + self.currency, + self.noonce, + self.number, + self.powmin, + self.time, + self.mediantime, + self.ud, + self.issuer, + self.prev_hash, + self.prev_issuer, + self.parameters, + self.members_count) + + for identity in self.identities: + doc += "{0}\n".format(identity.inline()) + + doc += "Joiners:\n" + for joiner in self.joiners: + doc += "{0}\n".format(joiner.inline()) + + doc += "Actives:\n" + for active in self.actives: + doc += "{0}\n".format(active.inline()) + + doc += "Leavers:\n" + for leaver in self.leavers: + doc += "{0]\n".format(leaver.inline()) + + doc += "Excluded:\n" + for exclude in self.exclude: + doc += "{0}\n".format(exclude.inline()) + + doc += "Certifications:\n" + for cert in self.certifications: + doc += "{0}\n".format(cert.inline()) + + doc += "Transactions:\n" + for transaction in self.transactions: + doc += "{0}\n".format(transaction.inline()) diff --git a/ucoinpy/documents/certification.py b/ucoinpy/documents/certification.py index 54104aad68cecae986f7692925a7c0e652b259ee..c5f0f09c028a7a85624fb661ec2f0daed6ba6c72 100644 --- a/ucoinpy/documents/certification.py +++ b/ucoinpy/documents/certification.py @@ -3,14 +3,38 @@ Created on 2 déc. 2014 @author: inso ''' +from . import Document -class Certification(object): + +class SelfCertification(Document): ''' - classdocs + A document discribing a self certification. ''' + def __init__(self, identifier): + self.identifier = identifier + + def uid(self): + return "UID:{0}".format(self.identifier) - def __init__(self, params): + def content(self): + return "{0}\n{1}".format(self.uid(), self.timestamp()) + + +class Certification(Document): + ''' + classdocs + ''' + + def __init__(self, selfcert, blockid): ''' Constructor ''' + self.selfcert = selfcert + self.blockid = blockid + + def timestamp(self): + return "META:TS:{0}".format(self.blockid) + + def content(self): + return "{0}\n{1}".format(self.selfcert.content(), self.timestamp()) diff --git a/ucoinpy/documents/membership.py b/ucoinpy/documents/membership.py index 9133840a78f2819bb02dc5b9901b080b8ba2c8ce..ae174521bd1bafa9efb0679104f2f28c64745c3c 100644 --- a/ucoinpy/documents/membership.py +++ b/ucoinpy/documents/membership.py @@ -3,14 +3,55 @@ Created on 2 déc. 2014 @author: inso ''' +from .. import PROTOCOL_VERSION + class Membership(object): ''' - classdocs + This is a utility class to generate membership documents : + Version: VERSION + Type: Membership + Currency: CURRENCY_NAME + Issuer: ISSUER + Block: NUMBER-HASH + Membership: MEMBERSHIP_TYPE + UserID: USER_ID + CertTS: CERTIFICATION_TS ''' - - def __init__(self, params): + def __init__(self, currency, issuer, block_number, block_hash, + membership_type, userid, cert_ts): ''' Constructor ''' + self.currency = currency + self.issuer = issuer + self.block_number = block_number + self.block_hash = block_hash + self.membership_type = membership_type + self.userid = userid + self.cert_ts = cert_ts + + def content(self): + return """ +Version: {0} +Type: Membership +Currency: {1} +Issuer: {2} +Block: {3}-{4} +Membership: {5} +UserID: {6} +CertTS: {7}""".format(PROTOCOL_VERSION, + self.currency, + self.issuer, + self.block_number, self.block_hash, + self.membership_type, + self.userid, + self.cert_ts) + + def inline(self): + return "{0}:{1}:{2}:{3}".format(self.issuer, + self.sign(), + self.block_number, + self.block_hash, + self.cert_ts) diff --git a/ucoinpy/documents/peer.py b/ucoinpy/documents/peer.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..082c06ef5d2e7655872c8a75167f9051e8256fbb 100644 --- a/ucoinpy/documents/peer.py +++ b/ucoinpy/documents/peer.py @@ -0,0 +1,84 @@ +''' +Created on 2 déc. 2014 + +@author: inso +''' + +import re + +from . import Document +from .. import PROTOCOL_VERSION, MANAGED_API + + +class Peer(Document): + """ + Version: VERSION + Type: Peer + Currency: CURRENCY_NAME + PublicKey: NODE_PUBLICKEY + Block: BLOCK + Endpoints: + END_POINT_1 + END_POINT_2 + END_POINT_3 + [...] + """ + + def __init__(self, currency, pubkey, blockid, endpoints): + self.currency = currency + self.pubkey = pubkey + self.blockid = blockid + self.endpoints = endpoints + + def content(self): + doc = """ +Version: {0} +Type: Peer +Currency: {1} +PublicKey: {2} +Block: {3} +Endpoints: +""".format(PROTOCOL_VERSION, self.currency, self.pubkey, self.blockid) + + for endpoint in self.endpoints: + doc += "{0}\n".format(endpoint.inline()) + return doc + + +class Endpoint(): + """ + Describing endpoints + """ + + @staticmethod + def from_inline(inline): + for api in MANAGED_API: + if (inline.startswith(api)): + return Endpoint.parse_line(inline, api) + + @staticmethod + def parse_line(self, inline, api): + if (api == "BASIC_MERKLED_API"): + bma_endpoints = re.compile('^BASIC_MERKLED_API( ([a-z_][a-z0-9-_.]+))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))$') + m = bma_endpoints.match(inline) + server = m.group(2) + ipv4 = m.group(4) + ipv6 = m.group(6) + port = int(m.group(8)) + return BMAEndpoint(server, ipv4, ipv6, port) + return None + + +class BMAEndpoint(Endpoint): + def __init__(self, server, ipv4, ipv6, port): + self.server = server + self.ipv4 = ipv4 + self.ipv6 = ipv6 + self.port = port + + def inline(self): + return "BASIC_MERKLED_API {DNS} {IPv4} {IPv6} {PORT}" \ + .format(DNS=self.server, + IPv4=self.ipv4, + IPv6=self.ipv6, + PORT=self.port) diff --git a/ucoinpy/documents/pubkey.py b/ucoinpy/documents/pubkey.py deleted file mode 100644 index 4a3a4796299102e7007ebf8eb1d4c42950a6df34..0000000000000000000000000000000000000000 --- a/ucoinpy/documents/pubkey.py +++ /dev/null @@ -1,16 +0,0 @@ -''' -Created on 2 déc. 2014 - -@author: inso -''' - -class Pubkey(object): - ''' - classdocs - ''' - - - def __init__(self, params): - ''' - Constructor - ''' diff --git a/ucoinpy/documents/status.py b/ucoinpy/documents/status.py index adf7bf5afa7b92dd07ceb8b2091b58c554b59c7b..dd11ce1af153ef774793acf3ef9b5676f807a6ae 100644 --- a/ucoinpy/documents/status.py +++ b/ucoinpy/documents/status.py @@ -4,13 +4,39 @@ Created on 2 déc. 2014 @author: inso ''' -class Status(object): +from . import Document +from .. import PROTOCOL_VERSION + + +class Status(Document): ''' - classdocs + Version: VERSION + Type: Status + Currency: CURRENCY_NAME + Status: STATUS + Block: BLOCK + From: SENDER + To: RECIPIENT ''' - - def __init__(self, params): + def __init__(self, currency, status, blockid, sender, recipient): ''' Constructor ''' + self.currency = currency + self.status = status + self.blockid = blockid + self.sender = sender + self.recipient = recipient + + def content(self): + return ''' +Version: {0} +Type: Status +Currency: {1} +Status: {2} +Block: {3} +From: {4} +To: {5} +'''.format(PROTOCOL_VERSION, self.currency, self.status, + self.blockid, self.sender, self.recipient) diff --git a/ucoinpy/documents/transaction.py b/ucoinpy/documents/transaction.py index f9ae19c41e7dfdc688e610f7985006ae8d4c06a8..10fa5d690c7f3f647792fd95f10ab620ea43cfe9 100644 --- a/ucoinpy/documents/transaction.py +++ b/ucoinpy/documents/transaction.py @@ -4,13 +4,148 @@ Created on 2 déc. 2014 @author: inso ''' -class Transaction(object): +from . import Document +from .. import PROTOCOL_VERSION + + +class Transaction(Document): ''' - classdocs +Version: VERSION +Type: Transaction +Currency: CURRENCY_NAME +Issuers: +PUBLIC_KEY +... +Inputs: +INDEX:SOURCE:NUMBER:FINGERPRINT:AMOUNT +... +Outputs: +PUBLIC_KEY:AMOUNT +... +Comment: COMMENT +SIGNATURES +... ''' + def __init__(self, currency, pubkeys, inputs, outputs, comment=None): + ''' + Constructor + ''' + self.currency = currency + self.pubkeys = pubkeys + self.inputs = inputs + self.outputs = outputs + self.comment = comment + + def content(self): + doc = """ +Version: {0} +Type: Transaction +Currency: {1} +Issuers:""".format(PROTOCOL_VERSION, + self.currency) + + for p in self.pubkeys: + doc += "{0}\n".format(p) + + doc += "Inputs:\n" + for i in self.inputs: + doc += "{0}\n".format(i.inline()) - def __init__(self, params): + doc += "Outputs:\n" + for o in self.outputs: + doc += "{0}\n".format(o.inline()) + + doc += """ +COMMENT: +{0} +""".format(self.comment) + + return doc + + def compact(self): + ''' + Return a transaction in its compact format. + ''' + """TX:VERSION:NB_ISSUERS:NB_INPUTS:NB_OUTPUTS:HAS_COMMENT +PUBLIC_KEY:INDEX +... +INDEX:SOURCE:FINGERPRINT:AMOUNT +... +PUBLIC_KEY:AMOUNT +... +COMMENT +""" + doc = "TX:{0}:{1}:{2}:{3}:{4}".format(PROTOCOL_VERSION, + self.pubkeys.len, + self.inputs.len, + self.outputs.len, + '1' if self.Comment else '0') + for pubkey in self.pubkeys: + doc += "{0}\n".format(pubkey) + for i in self.inputs: + doc += "{0}\n".format(i.compact()) + for o in self.outputs: + doc += "{0}\n".format(o.inline()) + if self.comment: + doc += "{0}\n".format(self.comment) + return doc + + def sign(self, keys): + signatures = "" + for k in keys: + signatures += "{0}\n".format(super().sign(k)) + return signatures + + +class SimpleTransaction(Transaction): + ''' +As transaction class, but for only one issuer. +... + ''' + def __init__(self, currency, pubkey, single_input, outputs, comment): ''' Constructor ''' + self.currency = currency + self.pubkeys = [pubkey] + self.inputs = [single_input] + self.outputs = outputs + self.comment = comment + + +class InputSource(): + ''' + A Transaction INPUT + ''' + def __init__(self, index, source, number, fingerprint, amount): + self.index = index + self.source = source + self.number = number + self.fingerprint = fingerprint + self.amount = amount + + def inline(self): + return "{0}:{1}:{2}:{3}:{4}".format(self.index, + self.source, + self.number, + self.fingerprint, + self.amount) + + def compact(self): + return "{0}:{1}:{2}:{3}".format(self.index, + self.source, + self.fingerprint, + self.amount) + + +class OutputSource(): + ''' + A Transaction OUTPUT + ''' + def __init__(self, pubkey, amount): + self.pubkey = pubkey + self.amount = amount + + def inline(self): + return "{0}:{1}".format(self.pubkey, self.amount) diff --git a/ucoinpy/key/__init__.py b/ucoinpy/key/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..49a5fec73d28ef80aef5b07e2663c970c0fd80d4 --- /dev/null +++ b/ucoinpy/key/__init__.py @@ -0,0 +1,17 @@ +''' +Ucoin public and private keys + +@author: inso +''' + +import base58 + + +class Base58Encoder(object): + @staticmethod + def encode(data): + return base58.b58encode(data) + + @staticmethod + def decode(data): + return base58.b58decode(data) diff --git a/ucoinpy/key/hdwallet.py b/ucoinpy/key/hdwallet.py new file mode 100644 index 0000000000000000000000000000000000000000..dffb5fffa8515b8d8433e9af2b965bf16ae3b459 --- /dev/null +++ b/ucoinpy/key/hdwallet.py @@ -0,0 +1,378 @@ +''' +HD Wallet inspired from Bip32 wallets. + +@author: inso +''' +''' +import os +import hmac +import hashlib +import ed25519 +import struct +import base58 +import base64 + +from hashlib import sha256 +from ecdsa.curves import SECP256k1 +from ecdsa.ecdsa import int_to_string, string_to_int +from ecdsa.numbertheory import square_root_mod_prime as sqrt_mod + +MIN_ENTROPY_LEN = 128 # bits +HDWALLET_HARDENED = 0x80000000 # choose from hardened set of child keys +CURVE_GEN = ecdsa.ecdsa.generator_secp256k1 +CURVE_ORDER = CURVE_GEN.order() +FIELD_ORDER = SECP256k1.curve.p() +INFINITY = ecdsa.ellipticcurve.INFINITY + + +class HDWalletKey(object): + + # Static initializers to create from entropy or external formats + # + @staticmethod + def fromEntropy(entropy, public=False): + "Create a HDWallet using supplied entropy >= MIN_ENTROPY_LEN" + if entropy == None: + entropy = os.urandom(MIN_ENTROPY_LEN/8) # Python doesn't have os.random() + if not len(entropy) >= MIN_ENTROPY_LEN/8: + raise ValueError("Initial entropy %i must be at least %i bits" % + (len(entropy), MIN_ENTROPY_LEN)) + I = hmac.new("UCoin seed", entropy, hashlib.sha512).digest() + + Il, Ir = I[:32], I[32:] + # FIXME test Il for 0 or less than SECP256k1 prime field order + key = HDWalletKey(secret=Il, chain=Ir, depth=0, index=0, fpr='\0\0\0\0', public=False) + if public: + key.SetPublic() + return key + + @staticmethod + def fromExtendedKey(xkey, public=False): + """ + Create a HDWallet by importing from extended private or public key string + + If public is True, return a public-only key regardless of input type. + """ + # Sanity checks + raw = base58.b58decode_check(xkey) + # To fix + #if len(raw) != 78: + # raise ValueError("extended key format wrong length") + + # Verify address version/type + #version = raw[:4] + #if version == EX_MAIN_PRIVATE: + # raise ValueError("unknown extended key version") + + # Extract remaining fields + depth = ord(raw[4]) + fpr = raw[5:9] + child = struct.unpack(">L", raw[9:13])[0] + chain = raw[13:45] + secret = raw[45:78] + + # Extract private key or public key point + if keytype == 'xprv': + secret = secret[1:] + else: + # Recover public curve point from compressed key + lsb = ord(secret[0]) & 1 + x = string_to_int(secret[1:]) + ys = (x**3+7) % FIELD_ORDER # y^2 = x^3 + 7 mod p + y = sqrt_mod(ys, FIELD_ORDER) + if y & 1 != lsb: + y = FIELD_ORDER-y + point = ecdsa.ellipticcurve.Point(SECP256k1.curve, x, y) + secret = ecdsa.VerifyingKey.from_public_point(point, curve=SECP256k1) + + is_pubkey = (keytype == 'xpub') + key = HDWalletKey(secret=secret, chain=chain, depth=depth, index=child, + fpr=fpr, public=is_pubkey) + if not is_pubkey and public: + key = key.SetPublic() + return key + + + # Normal class initializer + def __init__(self, secret, chain, depth, index, fpr, public=False): + """ + Create a public or private BIP32Key using key material and chain code. + + secret This is the source material to generate the keypair, either a + 32-byte string representation of a private key, or the ECDSA + library object representing a public key. + + chain This is a 32-byte string representation of the chain code + + depth Child depth; parent increments its own by one when assigning this + + index Child index + + fpr Parent fingerprint + + public If true, this keypair will only contain a public key and can only create + a public key chain. + """ + + self.public = public + if public is False: + self.k = ed25519.SigningKey(base58.b58decode(secret)) + self.K = self.k.get_verifying_key() + else: + self.k = None + self.K = secret + + self.C = chain + self.depth = depth + self.index = index + self.parent_fpr = fpr + + # Internal methods not intended to be called externally + def _hmac(self, data): + """ + Calculate the HMAC-SHA512 of input data using the chain code as key. + + Returns a tuple of the left and right halves of the HMAC + """ + I = hmac.new(self.C, data, hashlib.sha512).digest() + return (I[:32], I[32:]) + + def _CKDpriv(self, i): + """ + Create a child key of index 'i'. + + If the most significant bit of 'i' is set, then select from the + hardened key set, otherwise, select a regular child key. + + Returns a BIP32Key constructed with the child key parameters, + or None if i index would result in an invalid key. + """ + # Index as bytes, BE + i_str = struct.pack(">L", i) + + # Data to HMAC + if i & HDWALLET_HARDENED: + data = b'\0' + self.k.to_string() + i_str + else: + data = self.PublicKey() + i_str + # Get HMAC of data + (Il, Ir) = self._hmac(data) + + # Construct new key material from Il and current private key + Il_int = string_to_int(Il) + if Il_int > CURVE_ORDER: + return None + pvt_int = string_to_int(self.k.to_string()) + k_int = (Il_int + pvt_int) % CURVE_ORDER + if (k_int == 0): + return None + secret = (b'\0'*32 + int_to_string(k_int))[-32:] + + # Construct and return a new BIP32Key + return HDWalletKey(secret=secret, chain=Ir, depth=self.depth+1, + index=i, fpr=self.Fingerprint(), public=False) + + def _CKDpub(self, i): + """ + Create a publicly derived child key of index 'i'. + + If the most significant bit of 'i' is set, this is + an error. + + Returns a HDWalletKey constructed with the child key parameters, + or None if index would result in invalid key. + """ + + if i & HDWALLET_HARDENED: + raise Exception("Cannot create a hardened child key using public child derivation") + + # Data to HMAC. Same as CKDpriv() for public child key. + data = self.PublicKey() + struct.pack(">L", i) + + # Get HMAC of data + (Il, Ir) = self.hmac(data) + + # Construct curve point Il*G+K + Il_int = string_to_int(Il) + if Il_int >= CURVE_ORDER: + return None + point = Il_int*CURVE_GEN + self.K.pubkey.point + if point == INFINITY: + return None + + # Retrieve public key based on curve point + K_i = ed25519.VerifyingKey.from_public_point(point, curve=SECP256k1) + + # Construct and return a new BIP32Key + return HDWalletKey(secret=K_i, chain=Ir, depth=self.depth, index=i, fpr=self.Fingerprint(), public=True) + + + # Public methods + # + def ChildKey(self, i): + """ + Create and return a child key of this one at index 'i'. + + The index 'i' should be summed with BIP32_HARDEN to indicate + to use the private derivation algorithm. + """ + if self.public is False: + return self.CKDpriv(i) + else: + return self.CKDpub(i) + + + def SetPublic(self): + "Convert a private BIP32Key into a public one" + self.k = None + self.public = True + + + def PrivateKey(self): + "Return private key as string" + if self.public: + raise Exception("Publicly derived deterministic keys have no private half") + else: + return self.k.to_string() + + + def PublicKey(self): + "Return compressed public key encoding" + if self.K.pubkey.point.y() & 1: + ck = b'\3'+int_to_string(self.K.pubkey.point.x()) + else: + ck = b'\2'+int_to_string(self.K.pubkey.point.x()) + return ck + + + def ChainCode(self): + "Return chain code as string" + return self.C + + + def Identifier(self): + "Return key identifier as string" + cK = self.PublicKey() + return hashlib.new('ripemd160', sha256(cK).digest()).digest() + + + def Fingerprint(self): + "Return key fingerprint as string" + return self.Identifier()[:4] + + + def Address(self): + "Return compressed public key address" + vh160 = '\x00'+self.Identifier() + return Base58.check_encode(vh160) + + + def WalletImportFormat(self): + "Returns private key encoded for wallet import" + if self.public: + raise Exception("Publicly derived deterministic keys have no private half") + raw = '\x80' + self.k.to_string() + '\x01' # Always compressed + return Base58.check_encode(raw) + + + def ExtendedKey(self, private=True, encoded=True): + "Return extended private or public key as string, optionally Base58 encoded" + if self.public is True and private is True: + raise Exception("Cannot export an extended private key from a public-only deterministic key") + version = EX_MAIN_PRIVATE if private else EX_MAIN_PUBLIC + depth = chr(self.depth) + fpr = self.parent_fpr + child = struct.pack('>L', self.index) + chain = self.C + if self.public is True or private is False: + data = self.PublicKey() + else: + data = '\x00' + self.PrivateKey() + raw = version+depth+fpr+child+chain+data + if not encoded: + return raw + else: + return Base58.check_encode(raw) + + # Debugging methods + # + def dump(self): + "Dump key fields mimicking the BIP0032 test vector format" + print " * Identifier" + print " * (hex): ", self.Identifier().encode('hex') + print " * (fpr): ", self.Fingerprint().encode('hex') + print " * (main addr):", self.Address() + if self.public is False: + print " * Secret key" + print " * (hex): ", self.PrivateKey().encode('hex') + print " * (wif): ", self.WalletImportFormat() + print " * Public key" + print " * (hex): ", self.PublicKey().encode('hex') + print " * Chain code" + print " * (hex): ", self.C.encode('hex') + print " * Serialized" + print " * (pub hex): ", self.ExtendedKey(private=False, encoded=False).encode('hex') + print " * (prv hex): ", self.ExtendedKey(private=True, encoded=False).encode('hex') + print " * (pub b58): ", self.ExtendedKey(private=False, encoded=True) + print " * (prv b58): ", self.ExtendedKey(private=True, encoded=True) + + +if __name__ == "__main__": + import sys + + # BIP0032 Test vector 1 + entropy='000102030405060708090A0B0C0D0E0F'.decode('hex') + m = BIP32Key.fromEntropy(entropy) + print "Test vector 1:" + print "Master (hex):", entropy.encode('hex') + print "* [Chain m]" + m.dump() + + print "* [Chain m/0h]" + m = m.ChildKey(0+BIP32_HARDEN) + m.dump() + + print "* [Chain m/0h/1]" + m = m.ChildKey(1) + m.dump() + + print "* [Chain m/0h/1/2h]" + m = m.ChildKey(2+BIP32_HARDEN) + m.dump() + + print "* [Chain m/0h/1/2h/2]" + m = m.ChildKey(2) + m.dump() + + print "* [Chain m/0h/1/2h/2/1000000000]" + m = m.ChildKey(1000000000) + m.dump() + + # BIP0032 Test vector 2 + entropy = 'fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542'.decode('hex') + m = BIP32Key.fromEntropy(entropy) + print "Test vector 2:" + print "Master (hex):", entropy.encode('hex') + print "* [Chain m]" + m.dump() + + print "* [Chain m/0]" + m = m.ChildKey(0) + m.dump() + + print "* [Chain m/0/2147483647h]" + m = m.ChildKey(2147483647+BIP32_HARDEN) + m.dump() + + print "* [Chain m/0/2147483647h/1]" + m = m.ChildKey(1) + m.dump() + + print "* [Chain m/0/2147483647h/1/2147483646h]" + m = m.ChildKey(2147483646+BIP32_HARDEN) + m.dump() + + print "* [Chain m/0/2147483647h/1/2147483646h/2]" + m = m.ChildKey(2) + m.dump() +''' \ No newline at end of file