diff --git a/pywallet.py b/pywallet.py index 28a2929..af89668 100755 --- a/pywallet.py +++ b/pywallet.py @@ -51,7 +51,7 @@ from subprocess import * -max_version = 32500 +max_version = 40000 addrtype = 0 json_db = {} private_keys = [] @@ -150,7 +150,7 @@ def __init__( self, curve, x, y, order = None ): self.__order = order if self.__curve: assert self.__curve.contains_point( x, y ) if order: assert self * order == INFINITY - + def __add__( self, other ): if other == INFINITY: return self if self == INFINITY: return other @@ -331,7 +331,7 @@ def i2o_ECPublicKey(pkey): # hashes def hash_160(public_key): - md = hashlib.new('ripemd160') + md = hashlib.new('ripemd160') md.update(hashlib.sha256(public_key).digest()) return md.digest() @@ -353,7 +353,7 @@ def bc_address_to_hash_160(addr): __b58base = len(__b58chars) def b58encode(v): - """ encode v, which is a string of bytes, to base58. + """ encode v, which is a string of bytes, to base58. """ long_value = 0L @@ -491,8 +491,8 @@ def parse_BlockLocator(vds): return d def deserialize_BlockLocator(d): - result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') - return result + result = "Block Locator top: "+d['hashes'][0][::-1].encode('hex_codec') + return result def parse_setting(setting, vds): if setting[0] == "f": # flag (boolean) settings @@ -659,73 +659,73 @@ def read_device_size(size): class KEY: - def __init__ (self): - self.prikey = None - self.pubkey = None - - def generate (self, secret=None): - if secret: - exp = int ('0x' + secret.encode ('hex'), 16) - self.prikey = ecdsa.SigningKey.from_secret_exponent (exp, curve=secp256k1) - else: - self.prikey = ecdsa.SigningKey.generate (curve=secp256k1) - self.pubkey = self.prikey.get_verifying_key() - return self.prikey.to_der() - - def set_privkey (self, key): - if len(key) == 279: - seq1, rest = der.remove_sequence (key) - integer, rest = der.remove_integer (seq1) - octet_str, rest = der.remove_octet_string (rest) - tag1, cons1, rest, = der.remove_constructed (rest) - tag2, cons2, rest, = der.remove_constructed (rest) - point_str, rest = der.remove_bitstring (cons2) - self.prikey = ecdsa.SigningKey.from_string(octet_str, curve=secp256k1) - else: - self.prikey = ecdsa.SigningKey.from_der (key) - - def set_pubkey (self, key): - key = key[1:] - self.pubkey = ecdsa.VerifyingKey.from_string (key, curve=secp256k1) - - def get_privkey (self): - _p = self.prikey.curve.curve.p () - _r = self.prikey.curve.generator.order () - _Gx = self.prikey.curve.generator.x () - _Gy = self.prikey.curve.generator.y () - encoded_oid2 = der.encode_oid (*(1, 2, 840, 10045, 1, 1)) - encoded_gxgy = "\x04" + ("%64x" % _Gx).decode('hex') + ("%64x" % _Gy).decode('hex') - param_sequence = der.encode_sequence ( - ecdsa.der.encode_integer(1), - der.encode_sequence ( - encoded_oid2, - der.encode_integer (_p), - ), - der.encode_sequence ( - der.encode_octet_string("\x00"), - der.encode_octet_string("\x07"), - ), - der.encode_octet_string (encoded_gxgy), - der.encode_integer (_r), - der.encode_integer (1), - ); - encoded_vk = "\x00\x04" + self.pubkey.to_string () - return der.encode_sequence ( - der.encode_integer (1), - der.encode_octet_string (self.prikey.to_string ()), - der.encode_constructed (0, param_sequence), - der.encode_constructed (1, der.encode_bitstring (encoded_vk)), - ) - - def get_pubkey (self): - return "\x04" + self.pubkey.to_string() - - def sign (self, hash): - sig = self.prikey.sign_digest (hash, sigencode=ecdsa.util.sigencode_der) - return sig.encode('hex') - - def verify (self, hash, sig): - return self.pubkey.verify_digest (sig, hash, sigdecode=ecdsa.util.sigdecode_der) + def __init__ (self): + self.prikey = None + self.pubkey = None + + def generate (self, secret=None): + if secret: + exp = int ('0x' + secret.encode ('hex'), 16) + self.prikey = ecdsa.SigningKey.from_secret_exponent (exp, curve=secp256k1) + else: + self.prikey = ecdsa.SigningKey.generate (curve=secp256k1) + self.pubkey = self.prikey.get_verifying_key() + return self.prikey.to_der() + + def set_privkey (self, key): + if len(key) == 279: + seq1, rest = der.remove_sequence (key) + integer, rest = der.remove_integer (seq1) + octet_str, rest = der.remove_octet_string (rest) + tag1, cons1, rest, = der.remove_constructed (rest) + tag2, cons2, rest, = der.remove_constructed (rest) + point_str, rest = der.remove_bitstring (cons2) + self.prikey = ecdsa.SigningKey.from_string(octet_str, curve=secp256k1) + else: + self.prikey = ecdsa.SigningKey.from_der (key) + + def set_pubkey (self, key): + key = key[1:] + self.pubkey = ecdsa.VerifyingKey.from_string (key, curve=secp256k1) + + def get_privkey (self): + _p = self.prikey.curve.curve.p () + _r = self.prikey.curve.generator.order () + _Gx = self.prikey.curve.generator.x () + _Gy = self.prikey.curve.generator.y () + encoded_oid2 = der.encode_oid (*(1, 2, 840, 10045, 1, 1)) + encoded_gxgy = "\x04" + ("%64x" % _Gx).decode('hex') + ("%64x" % _Gy).decode('hex') + param_sequence = der.encode_sequence ( + ecdsa.der.encode_integer(1), + der.encode_sequence ( + encoded_oid2, + der.encode_integer (_p), + ), + der.encode_sequence ( + der.encode_octet_string("\x00"), + der.encode_octet_string("\x07"), + ), + der.encode_octet_string (encoded_gxgy), + der.encode_integer (_r), + der.encode_integer (1), + ); + encoded_vk = "\x00\x04" + self.pubkey.to_string () + return der.encode_sequence ( + der.encode_integer (1), + der.encode_octet_string (self.prikey.to_string ()), + der.encode_constructed (0, param_sequence), + der.encode_constructed (1, der.encode_bitstring (encoded_vk)), + ) + + def get_pubkey (self): + return "\x04" + self.pubkey.to_string() + + def sign (self, hash): + sig = self.prikey.sign_digest (hash, sigencode=ecdsa.util.sigencode_der) + return sig.encode('hex') + + def verify (self, hash, sig): + return self.pubkey.verify_digest (sig, hash, sigdecode=ecdsa.util.sigdecode_der) def bool_to_int(b): if b: @@ -1200,16 +1200,34 @@ def item_callback(type, d): if vers > -1: addrtype = oldaddrtype - -def importprivkey(db, sec, label, reserve, keyishex): - if keyishex is None: - pkey = regenerate_key(sec) - elif len(sec) == 64: - pkey = EC_KEY(str_to_long(sec.decode('hex'))) +def short_to_hex(shortKey): + chk = hashlib.sha256('%s?' % shortKey).digest() + if chk[0] == '\x00': + return hashlib.sha256(shortKey).hexdigest() + elif chk[0] == '\x01': + # TODO deal with PBKDF2 mini keys, see https://en.bitcoin.it/wiki/Mini_private_key_format#Example_with_PBKDF2 + print("Invalid mini private key") + exit(0) else: - print("Hexadecimal private keys must be 64 characters long") + print("Invalid mini private key") exit(0) +def importprivkey(db, sec, label, reserve, keyishex, keyismini): + if keyishex is not None: + if len(sec) == 64: + pkey = EC_KEY(str_to_long(sec.decode('hex'))) + else: + print("Hexadecimal private keys must be 64 characters long") + exit(0) + elif keyismini is not None: + if len(sec) in [22,26,30]: + pkey = EC_KEY(str_to_long(short_to_hex(sec).decode('hex'))) + else: + print("Mini private key format must be 22, 26 or 30 characters long") + exit(0) + else: + pkey = regenerate_key(sec) + if not pkey: return False @@ -1248,14 +1266,21 @@ def write_jsonfile(filename, array): filout.write(json.dumps(array, sort_keys=True, indent=0)) filout.close() -def keyinfo(sec, keyishex): - if keyishex is None: - pkey = regenerate_key(sec) - elif len(sec) == 64: - pkey = EC_KEY(str_to_long(sec.decode('hex'))) +def keyinfo(sec, keyishex, keyismini): + if keyishex is not None: + if len(sec) == 64: + pkey = EC_KEY(str_to_long(sec.decode('hex'))) + else: + print("Hexadecimal private keys must be 64 characters long") + exit(0) + elif keyismini is not None: + if len(sec) in [22,26,30]: + pkey = EC_KEY(str_to_long(short_to_hex(sec).decode('hex'))) + else: + print("Mini private key format must be 22, 26 or 30 characters long") + exit(0) else: - print("Hexadecimal private keys must be 64 characters long") - exit(0) + pkey = regenerate_key(sec) if not pkey: return False @@ -1334,7 +1359,7 @@ def X_if_else(iftrue, cond, iffalse): if 'twisted' not in missing_dep: class WIRoot(resource.Resource): - def render_GET(self, request): + def render_GET(self, request): header = '
%s'%(wdir, wname, json.dumps(json_db, sort_keys=True, indent=4)) - except: - log.err() - return 'Error in dump page' + read_wallet(json_db, create_env(wdir), wname, True, True, "", None, version) + return 'Wallet: %s/%s
%s'%(wdir, wname, json.dumps(json_db, sort_keys=True, indent=4)) + except: + log.err() + return 'Error in dump page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIDumpTx(resource.Resource): - def render_GET(self, request): - try: - wdir=request.args['dir'][0] - wname=request.args['name'][0] - jsonfile=request.args['file'][0] - log.msg('Wallet Dir: %s' %(wdir)) - log.msg('Wallet Name: %s' %(wname)) - - if not os.path.isfile(wdir+"/"+wname): - return '%s/%s doesn\'t exist'%(wdir, wname) - if os.path.isfile(jsonfile): - return '%s exists'%(jsonfile) - - read_wallet(json_db, create_env(wdir), wname, True, True, "", None) - write_jsonfile(jsonfile, json_db['tx']) - return 'Wallet: %s/%s
hash: %s\n%d transaction%s imported in %s/%s" % (inverse_str(txk[6:]), i, iais(i), wdir, wname) - except: - log.err() - return 'Error in importtx page' + except: + log.err() + return 'Error in importtx page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIImport(resource.Resource): - def render_GET(self, request): - global addrtype - try: + def render_GET(self, request): + global addrtype + try: sec = request.args['key'][0] format = request.args['format'][0] addrtype = int(request.args['vers'][0]) @@ -1801,17 +1826,17 @@ def render_GET(self, request): return "Address: %s\nPrivkey: %s\nHexkey: %s\nKey imported in %s/%s" % (addr, SecretToASecret(secret), secret.encode('hex'), wdir, wname) - except: - log.err() - return 'Error in import page' + except: + log.err() + return 'Error in import page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WI404(resource.Resource): - def render_GET(self, request): - return 'Page Not Found' + def render_GET(self, request): + return 'Page Not Found' from optparse import OptionParser @@ -1832,6 +1857,9 @@ def render_GET(self, request): parser.add_option("--importhex", dest="keyishex", action="store_true", help="KEY is in hexadecimal format") + parser.add_option("--importmini", dest="keyismini", action="store_true", + help="KEY is in mini private key format") + parser.add_option("--datadir", dest="datadir", help="wallet directory (defaults to bitcoin default)") @@ -1915,7 +1943,7 @@ def render_GET(self, request): i = 0 for sec in keys: print("\nImporting key %4d/%d:"%(i+1, len(keys))) - importprivkey(db, sec, "recovered: %s"%sec, None, True) + importprivkey(db, sec, "recovered: %s"%sec, None, True, None) i += 1 db.close() @@ -1937,13 +1965,13 @@ def render_GET(self, request): if 'twisted' not in missing_dep: VIEWS = { - 'DumpWallet': WIDumpWallet(), - 'Import': WIImport(), - 'ImportTx': WIImportTx(), - 'DumpTx': WIDumpTx(), - 'Info': WIInfo(), - 'Delete': WIDelete(), - 'Balance': WIBalance() + 'DumpWallet': WIDumpWallet(), + 'Import': WIImport(), + 'ImportTx': WIImportTx(), + 'DumpTx': WIDumpTx(), + 'Info': WIInfo(), + 'Delete': WIDelete(), + 'Balance': WIBalance() } if options.datadir is not None: @@ -1991,7 +2019,7 @@ def render_GET(self, request): addrtype = int(options.otherversion) if options.keyinfo is not None: - if not keyinfo(options.key, options.keyishex): + if not keyinfo(options.key, options.keyishex, options.keyismini): print "Bad private key" exit(0) @@ -2005,14 +2033,15 @@ def render_GET(self, request): print json.dumps(json_db, sort_keys=True, indent=4) elif options.key: if json_db['version'] > max_version: - print "Version mismatch (must be <= %d)" % max_version - elif (options.keyishex is None and options.key in private_keys) or (options.keyishex is not None and options.key in private_hex_keys): + print "Version mismatch (must be <= %d, is %d)" % (max_version, json_db['version']) + elif ((options.keyishex is None and options.keyismini is None) and options.key in private_keys) or (options.keyishex is not None and options.key in private_hex_keys) or (options.keyismini is not None and short_to_hex(options.key) in private_hex_keys): print "Already exists" else: db = open_wallet(db_env, determine_db_name(), writable=True) - if importprivkey(db, options.key, options.label, options.reserve, options.keyishex): + if importprivkey(db, options.key, options.label, options.reserve, options.keyishex, options.keyismini): print "Imported successfully" + print "You may need to run 'bitcoin -rescan' to see any associated transactions" else: print "Bad private key"