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 = '

Pywallet Web Interface

CLOSE BITCOIN BEFORE USE!



' DWForm = WI_FormInit('Dump your wallet:', 'DumpWallet') + \ @@ -1441,97 +1466,97 @@ def render_GET(self, request): page = 'Pywallet Web Interface' + header + Javascript + DWForm + DTxForm + InfoForm + ImportForm + ImportTxForm + DeleteForm + BalanceForm + Misc + '' return page - def getChild(self, name, request): - if name == '': - return self - else: - if name in VIEWS.keys(): - return resource.Resource.getChild(self, name, request) - else: - return WI404() + def getChild(self, name, request): + if name == '': + return self + else: + if name in VIEWS.keys(): + return resource.Resource.getChild(self, name, request) + else: + return WI404() class WIDumpWallet(resource.Resource): - def render_GET(self, request): - try: - wdir=request.args['dir'][0] - wname=request.args['name'][0] - version = int(request.args['version'][0]) - log.msg('Wallet Dir: %s' %(wdir)) - log.msg('Wallet Name: %s' %(wname)) + def render_GET(self, request): + try: + wdir=request.args['dir'][0] + wname=request.args['name'][0] + version = int(request.args['version'][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 not os.path.isfile(wdir+"/"+wname): + return '%s/%s doesn\'t exist'%(wdir, wname) - read_wallet(json_db, create_env(wdir), wname, True, True, "", None, version) - return 'Wallet: %s/%s
Dump:
%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
Dump:
%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
Transations dumped in %s'%(wdir, wname, jsonfile) - except: - log.err() - return 'Error in dumptx page' - - def render_POST(self, request): - return self.render_GET(request) + 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
Transations dumped in %s'%(wdir, wname, jsonfile) + except: + log.err() + return 'Error in dumptx page' + + def render_POST(self, request): + return self.render_GET(request) class WIBalance(resource.Resource): - def render_GET(self, request): - try: - return "%s"%str(balance(balance_site, request.args['key'][0]).encode('utf-8')) - except: - log.err() - return 'Error in balance page' + def render_GET(self, request): + try: + return "%s"%str(balance(balance_site, request.args['key'][0]).encode('utf-8')) + except: + log.err() + return 'Error in balance page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIDelete(resource.Resource): - def render_GET(self, request): - try: - wdir=request.args['dir'][0] - wname=request.args['name'][0] - keydel=request.args['keydel'][0] - typedel=request.args['typedel'][0] - db_env = create_env(wdir) + def render_GET(self, request): + try: + wdir=request.args['dir'][0] + wname=request.args['name'][0] + keydel=request.args['keydel'][0] + typedel=request.args['typedel'][0] + db_env = create_env(wdir) - if not os.path.isfile(wdir+"/"+wname): - return '%s/%s doesn\'t exist'%(wdir, wname) + if not os.path.isfile(wdir+"/"+wname): + return '%s/%s doesn\'t exist'%(wdir, wname) - deleted_items = delete_from_wallet(db_env, wname, typedel, keydel) + deleted_items = delete_from_wallet(db_env, wname, typedel, keydel) - return "%s:%s has been successfully deleted from %s/%s, resulting in %d deleted item%s"%(typedel, keydel, wdir, wname, deleted_items, iais(deleted_items)) + return "%s:%s has been successfully deleted from %s/%s, resulting in %d deleted item%s"%(typedel, keydel, wdir, wname, deleted_items, iais(deleted_items)) - except: - log.err() - return 'Error in delete page' + except: + log.err() + return 'Error in delete page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) def message_to_hash(msg, msgIsHex=False): str = "" @@ -1631,9 +1656,9 @@ def inverse_str(string): if 'twisted' not in missing_dep: class WIInfo(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]) @@ -1702,19 +1727,19 @@ def render_GET(self, request): return ret - except: - log.err() - return 'Error in info page' + except: + log.err() + return 'Error in info page' - def render_POST(self, request): - return self.render_GET(request) + def render_POST(self, request): + return self.render_GET(request) class WIImportTx(resource.Resource): - def render_GET(self, request): - global addrtype - try: + def render_GET(self, request): + global addrtype + try: wdir=request.args['dir'][0] wname=request.args['name'][0] txk=request.args['txk'][0] @@ -1747,18 +1772,18 @@ def render_GET(self, request): return "
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"