diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81e47d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*~ +*.pyc +*.pyo +/bip32utils.egg-info +/dist diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..474afcd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright 2014 Corgan Labs + +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/bin/bip32gen b/bin/bip32gen new file mode 100755 index 0000000..da40a54 --- /dev/null +++ b/bin/bip32gen @@ -0,0 +1,245 @@ +#!/usr/bin/env python +# +# Copyright 2014 Corgan Labs +# See LICENSE.txt for distribution terms +# + +import os, sys, argparse, re +from bip32utils import * + +# __main__ entrypoint at bottom + +def ReadInput(fname, count, is_hex): + "Read input from either stdin or file, optionally hex decoded" + f = sys.stdin if fname == '-' else open(fname, 'rb') + + with f: + if count is not None: + n = count*2 if is_hex else count + data = f.read(n) + else: + data = f.read() + + if is_hex: + data = data.strip().decode('hex') + return data + + +def WriteOutput(fname, prefix, data, is_hex): + "Write output to either stdout or file, optionally hex encoded" + if is_hex: + data = data.encode('hex') + f= sys.stdout if fname == '-' else open(fname, 'w') + f.write(prefix+data+'\n') + + +def ParseKeyspec(args, input_data, keyspec, cache): + """ + Create a key from key specification with common args, storing + intermediate keys in a cache for reuse. + + Assumes keyspec format already validated with regex. + + There are three sources of input for key generation: + + * Supplying entropy from stdin or file, from which + the master key and seed will be generated per BIP0032. + This is one option for keyspecs starting with 'm/...' or + 'M/...'. Keyspecs starting with 'M' result in public- + only keys. From this master key, they keyspec is + recursively parsed to create child keys using the indices + found in the keyspec. + + * Supplying an extended private key, which is imported back + into a normal key. From this, normal or hardened child keys + are recursively derived using the private derivation algorithm + and index numbers in the keyspec. The returned key is capable + of generating further normal or hardened child keys. + + * Supplying an extended public key, which is imported back into + a public-only key. From this, public-only keys are recursively + derived using the public derivation algorithm and index numbers + in the keyspec. The returned key does not have a private key half + and is only further capable of generating publicly derived child + keys. + """ + key = None + acc = '' + + # Generate initial key, either from entropy, xprv, or xpub + if args.input_type == 'entropy': + acc = keyspec.split('/')[0] + try: + key = cache[acc] + except KeyError: + public = (acc == 'M') + key = BIP32Key.fromEntropy(entropy=input_data['entropy'], public=public) + cache[acc] = key + elif args.input_type == 'xprv': + try: + key = cache['xprv'] + except KeyError: + key = BIP32Key.fromExtendedKey(input_data['xprv']) + cache['xprv'] = key + else: + try: + key = cache['xpub'] + except KeyError: + key = BIP32Key.fromExtendedKey(input_data['xpub']) + cache['xpub'] = key + + # Parse nodes, build up intermediate keys + for node in keyspec.split('/'): + if node in ['m', 'M']: + key = cache[node] + else: + # Descendent or relative node + if acc == '': + acc = node + else: + acc = acc + '/' + node + + try: + key = cache[acc] + except KeyError: + if key is None: + key = cache[args.input_type] + # Now generate child keys + i = int(node.split('h')[0]) + if 'h' in node: + i = i + BIP32_HARDEN + key = key.ChildKey(i) + cache[acc] = key + return key + + +# Input sources + +def ReadEntropy(args): + "Reads optionally hex-encoded data from source" + entropy = ReadInput(args.from_file, args.amount/8, args.input_hex) + if len(entropy) < args.amount/8: + raise Exception("Insufficient entropy provided") + if args.verbose: + src = 'stdin' if args.from_file == '-' else args.from_file + print "Creating master key and seed using %i bits of entropy read from" % args.amount, src + print "entropy:", entropy.encode('hex') + return entropy + + +valid_output_types = ['addr','privkey','wif','pubkey','xprv','xpub','chain'] + +def GetArgs(): + "Parse command line and validate inputs" + parser = argparse.ArgumentParser(description='Create hierarchical deterministic wallet addresses') + parser.add_argument('-x', '--input-hex', action='store_true', default=False, + help='input supplied as hex-encoded ascii') + parser.add_argument('-X', '--output-hex', action='store_true', default=False, + help='output generated (where applicable) as hex-encoded ascii') + parser.add_argument('-i', '--input-type', choices=['entropy','xprv','xpub'], required=True, action='store', + help='source material to generate key') + parser.add_argument('-n', '--amount', type=int, default=128, action='store', + help='amount of entropy to to read (bits), None for all of input') + parser.add_argument('-f', '--from-file', action='store', default='-', + help="filespec of input data, '-' for stdin") + parser.add_argument('-F', '--to-file', action='store', default='-', + help="filespec of output data, '-' for stdout") + parser.add_argument('-o', '--output-type', action='store', required=True, + help='output types, comma separated, from %s' % '|'.join(valid_output_types)) + parser.add_argument('-v', '--verbose', action='store_true', default=False, + help='verbose output, not for machine parsing') + parser.add_argument('-d', '--debug', action='store_true', default=False, + help='enable debugging output') + parser.add_argument('chain', nargs='+', + help='list of hierarchical key specifiers') + + args = parser.parse_args() + + # Validate -f, --from-file is readable + ff = args.from_file + if ff != '-' and os.access(ff, os.R_OK) is False: + raise ValueError("unable to read from %s, aborting" % ff) + + # Validate -F, --to-file parent dir is writeable + tf = args.to_file + if tf != '-': + pd = os.path.dirname(os.path.abspath(tf)) + if os.access(pd, os.W_OK) is False: + raise ValueError("do not have permissions to create file in %s, aborting\n" % pd) + + # Validate -o, --output-type + for o in args.output_type.split(','): + if o not in valid_output_types: + valid_output_display = '['+'|'.join(valid_output_types)+']' + raise ValueError("output type \'%s\' is not one of %s\n" % (o, valid_output_display)) + + # Validate keyspecs for syntax + for keyspec in args.chain: + if not re.match("^([mM]|[0-9]+h?)(/[0-9]+h?)*$", keyspec): + raise ValueError("chain %s is not valid\n" % keyspec) + # If input is from entropy, keyspec must be absolute + elif args.input_type == 'entropy' and keyspec[0] not in 'mM': + raise ValueError("When generating from entropy, keyspec must start with 'm' or 'M'") + # Importing extended private or public keys need relative keyspecs + elif args.input_type in ['xpub','xprv'] and keyspec[0] in 'mM': + raise ValueError("When generating from xprv or xpub, keyspec must start with 0..9") + + return args + + +def ErrorExit(e): + "Hard bailout printing exception" + sys.stderr.write(sys.argv[0]+": "+e.message+'\n') + sys.exit(1) + + +if __name__ == "__main__": + try: + args = GetArgs() + except Exception as e: + ErrorExit(e) + + # Get common input data + input_data = {} + try: + if args.input_type == 'entropy': + input_data['entropy'] = ReadEntropy(args) + elif args.input_type == 'xprv': + if args.verbose: + print "Importing starting key from extended private key" + input_data['xprv'] = ReadInput(args.from_file, None, False).strip() + elif args.input_type == 'xpub': + if args.verbose: + print "Importing starting key from extended public key" + input_data['xpub'] = ReadInput(args.from_file, None, False).strip() + except Exception as e: + ErrorExit(e) + + # Iterate through keyspecs, create, then output + cache = {} + otypes = args.output_type.split(',') + + for keyspec in args.chain: + if args.verbose: + print "Keyspec:", keyspec + key = ParseKeyspec(args, input_data, keyspec, cache) + + # Output fields in command-line supplied order + for otype in otypes: + prefix = '' if not args.verbose else otype+':'+' '*(8-len(otype)) + if otype == 'addr': + WriteOutput(args.to_file, prefix, key.Address(), False) + elif otype == 'privkey': + WriteOutput(args.to_file, prefix, key.PrivateKey(), args.output_hex) + elif otype == 'wif': + WriteOutput(args.to_file, prefix, key.WalletImportFormat(), False) + elif otype == 'pubkey': + WriteOutput(args.to_file, prefix, key.PublicKey(), args.output_hex) + elif otype == 'xprv': + WriteOutput(args.to_file, prefix, key.ExtendedKey(private=True, encoded=True), False) + elif otype == 'xpub': + WriteOutput(args.to_file, prefix, key.ExtendedKey(private=False, encoded=True), False) + elif otype == 'chain': + WriteOutput(args.to_file, prefix, key.ChainCode(), args.output_hex) + if args.verbose: + WriteOutput(args.to_file, '', '', False) diff --git a/bip32utils/BIP32Key.py b/bip32utils/BIP32Key.py new file mode 100755 index 0000000..2418808 --- /dev/null +++ b/bip32utils/BIP32Key.py @@ -0,0 +1,376 @@ +#!/usr/bin/env python +# +# Copyright 2014 Corgan Labs +# See LICENSE.txt for distribution terms +# + +import os +import hmac +import hashlib +import ecdsa +import struct +import Base58 + +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 +BIP32_HARDEN = 0x80000000 # choose from hardened set of child keys +CURVE_GEN = ecdsa.ecdsa.generator_secp256k1 +CURVE_ORDER = CURVE_GEN.order() +CURVE_PRIME = SECP256k1.curve.p() + +class BIP32Key(object): + + # Static initializers to create from entropy or external formats + # + @staticmethod + def fromEntropy(entropy, public=False): + "Create a BIP32Key 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 wallet entropy %i must be at least %i bits" % + (len(entropy), MIN_ENTROPY_LEN)) + I = hmac.new("Bitcoin seed", entropy, hashlib.sha512).digest() + Il, Ir = I[:32], I[32:] + # FIXME test Il for 0 or less than SECP256k1 prime field order + key = BIP32Key(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 BIP32Key by importing from extended private or public key string + + If public is True, return a public-only key regardless of input type. + """ + rawchk = Base58.decode(xkey) + if len(rawchk) != 82: + raise ValueError("extended key format wrong length") + + # Verify encoding checksum + raw, chk = rawchk[:-4], rawchk[-4:] + if (chk != sha256(sha256(raw).digest()).digest()[:4]): + raise ValueError("base58 decoding checksum failure") + + # Verify address version/type + version = raw[:4] + if version.encode('hex') == '0488ade4': + keytype = 'xprv' + elif version.encode('hex') == '0488b21e': + keytype = 'xpub' + else: + 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) % CURVE_PRIME # y^2 = x^3 + 7 mod p + y = sqrt_mod(ys, CURVE_PRIME) + if y & 1 != lsb: + y = CURVE_PRIME-y + point = ecdsa.ellipticcurve.Point(SECP256k1.curve, x, y) + secret = ecdsa.VerifyingKey.from_public_point(point, curve=SECP256k1) + + is_pubkey = (keytype == 'xpub') + key = BIP32Key(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 = ecdsa.SigningKey.from_string(secret, curve=SECP256k1) + 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 HMAC and its left and right halves. + """ + I = hmac.new(self.C, data, hashlib.sha512).digest() + return (I, 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. + """ + # Index as bytes, BE + i_str = struct.pack(">L", i) + + # Data to HMAC + if i & BIP32_HARDEN: + data = b'\0' + self.k.to_string() + i_str + else: + data = self.PublicKey() + i_str + + # Get HMAC of data + (I, Il, Ir) = self.hmac(data) + + # Construct new key material from Il and current private key + Il_int = string_to_int(Il) + pvt_int = string_to_int(self.k.to_string()) + k_int = (Il_int + pvt_int) % CURVE_ORDER + secret = int_to_string(k_int) + + # Construct and return a new BIP32Key + return BIP32Key(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 BIP32Key constructed with the child key parameters. + """ + + if i & BIP32_HARDEN: + 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 + (I, Il, Ir) = self.hmac(data) + + # Construct curve point Il*G+K + Il_int = string_to_int(Il) + point = Il_int*CURVE_GEN + self.K.pubkey.point + + # Retrieve public key based on curve point + K_i = ecdsa.VerifyingKey.from_public_point(point, curve=SECP256k1) + + # Construct and return a new WalletNode + return BIP32Key(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() + raw = vh160+sha256(sha256(vh160).digest()).digest()[:4] + return Base58.encode(raw) + + + 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 + chk = sha256(sha256(raw).digest()).digest()[:4] + return Base58.encode(raw+chk) + + + 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 = '\x04\x88\xB2\x1E' if private is False else '\x04\x88\xAD\xE4' + 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: + chk = sha256(sha256(raw).digest()).digest()[:4] + return Base58.encode(raw+chk) + + # 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() diff --git a/bip32utils/Base58.py b/bip32utils/Base58.py new file mode 100755 index 0000000..1456c34 --- /dev/null +++ b/bip32utils/Base58.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# +# Copyright 2014 Corgan Labs +# See LICENSE.txt for distribution terms +# + +__base58_alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' +__base58_radix = len(__base58_alphabet) + + +def __string_to_int(data): + "Convert string of bytes Python integer, MSB" + val = 0 + for (i, c) in enumerate(data[::-1]): + val += (256**i)*ord(c) + return val + + +def encode(data): + "Encode string into Bitcoin base58" + enc = '' + val = __string_to_int(data) + while val >= __base58_radix: + val, mod = divmod(val, __base58_radix) + enc = __base58_alphabet[mod] + enc + if val: + enc = __base58_alphabet[val] + enc + + # Pad for leading zeroes + n = len(data)-len(data.lstrip('\0')) + return __base58_alphabet[0]*n + enc + + +def decode(data): + "Decode Bitcoin base58 format to string" + val = 0 + for (i, c) in enumerate(data[::-1]): + val += __base58_alphabet.find(c) * (__base58_radix**i) + dec = '' + while val >= 256: + val, mod = divmod(val, 256) + dec = chr(mod) + dec + if val: + dec = chr(val) + dec + return dec + + +if __name__ == '__main__': + assert(__base58_radix == 58) + data = 'now is the time for all good men to come to the aid of their country' + enc = encode(data) + assert(decode(enc) == data) diff --git a/bip32utils/__init__.py b/bip32utils/__init__.py new file mode 100644 index 0000000..313f08c --- /dev/null +++ b/bip32utils/__init__.py @@ -0,0 +1,2 @@ +from BIP32Key import BIP32Key, BIP32_HARDEN + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..6b31892 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + name = 'bip32utils', + version = '0.1', + author = 'Johnathan Corgan, Corgan Labs', + author_email = 'johnathan@corganlabs.com', + url = 'http://github.com/jmcorgan/bip32utils', + description = 'Utilites for generating and using Bitcoin hierarchical deterministic wallets (BIP0032).', + license = 'MIT', + requires = ['ecsda'], + packages = ['bip32utils'], + scripts = ['bin/bip32gen'] +) diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..1053c0d --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +*.asc diff --git a/tests/bip0032-vectors.sh b/tests/bip0032-vectors.sh new file mode 100755 index 0000000..038fd0e --- /dev/null +++ b/tests/bip0032-vectors.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# BIP0032 Test vector #1 + +echo Generating BIP0032 test vector 1: +echo 000102030405060708090A0B0C0D0E0F | \ + bip32gen -v \ + -i entropy -f - -x \ + -o privkey,wif,pubkey,addr,xprv,xpub -F - -X \ + m \ + m/0h \ + m/0h/1 \ + m/0h/1/2 \ + m/0h/1/2h/2 \ + m/0h/1/2h/2/1000000000 + +# BIP0032 Test vector #2 + +echo Generating BIP0032 test vector 2: +echo fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542 | \ + bip32gen -v \ + -i entropy -f - -x -n 512 \ + -o privkey,wif,pubkey,addr,xprv,xpub -F - -X \ + m \ + m/0 \ + m/0/2147483647h \ + m/0/2147483647h/1 \ + m/0/2147483647h/1/2147483646h \ + m/0/2147483647h/1/2147483646h/2 diff --git a/tests/import-xkey.sh b/tests/import-xkey.sh new file mode 100755 index 0000000..2a452d7 --- /dev/null +++ b/tests/import-xkey.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +XPRV=$(bip32gen -i entropy -f vector1.bin -o xprv -F - m/0h) +echo "Extended private key for 'm/0h' is $XPRV" +echo $XPRV >xprv.asc +XPUB=$(bip32gen -i entropy -f vector1.bin -o xpub -F - m/0h) +echo "Extended public key for 'm/0h' is $XPUB" +echo $XPUB >xpub.asc +ADDR=$(bip32gen -i entropy -f vector1.bin -o addr m/0h/1) +echo "Using entropy for 'm', address of 'm/0h/1' is $ADDR" +ADDR2=$(bip32gen -i xprv -f xprv.asc -o addr 1) +echo "Using xprv for 'm/0h', address of 'm/0h/1' is $ADDR2" +ADDR3=$(bip32gen -i xpub -f xpub.asc -o addr 1) +echo "Using xpub for 'M/0h', address of 'M/0h/1' is $ADDR3" diff --git a/tests/pubtest.sh b/tests/pubtest.sh new file mode 100755 index 0000000..f08fe3d --- /dev/null +++ b/tests/pubtest.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +echo Generating receiving addresses through both private and public chains using entropy +bip32gen -v \ + -i entropy -f vector1.bin \ + -o addr -F - -X \ + m/0/1/2/3 M/0/1/2/3 diff --git a/tests/vector1.bin b/tests/vector1.bin new file mode 100644 index 0000000..b66efb8 Binary files /dev/null and b/tests/vector1.bin differ