Add web interface
This commit is contained in:
parent
1f9c16553f
commit
ca80db790c
5
README
5
README
@ -11,10 +11,15 @@ Options:
|
||||
--label=LABEL label shown in the adress book (defaults to '')
|
||||
--testnet use testnet subdirectory and address type
|
||||
--namecoin use namecoin address type
|
||||
--otherversion=OTHERVERSION
|
||||
use other network address type, whose version is
|
||||
OTHERVERSION
|
||||
--info display pubkey, privkey (both depending on the
|
||||
network) and hexkey
|
||||
--reserve import as a reserve key, i.e. it won't show in the
|
||||
adress book
|
||||
--balance=KEY_BALANCE
|
||||
prints balance of KEY_BALANCE
|
||||
--web run pywallet web interface
|
||||
--port=PORT port of web interface (defaults to 8989)
|
||||
|
||||
|
||||
291
pywallet.py
291
pywallet.py
@ -19,6 +19,13 @@ import hashlib
|
||||
import random
|
||||
import urllib
|
||||
|
||||
from twisted.internet import reactor
|
||||
from twisted.web import server, resource
|
||||
from twisted.web.static import File
|
||||
from twisted.python import log
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
max_version = 32500
|
||||
addrtype = 0
|
||||
json_db = {}
|
||||
@ -30,7 +37,7 @@ for i in range(256):
|
||||
aversions[i] = "version %d" % i;
|
||||
aversions[0] = 'Bitcoin';
|
||||
aversions[52] = 'Namecoin';
|
||||
aversions[111] = 'Bitcoin testnet';
|
||||
aversions[111] = 'Testnet';
|
||||
|
||||
def determine_db_dir():
|
||||
import os
|
||||
@ -853,9 +860,256 @@ def keyinfo(sec, keyishex):
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
class WIRoot(resource.Resource):
|
||||
|
||||
global max_version, addrtype
|
||||
def render_GET(self, request):
|
||||
header = '<h1>Pywallet Web Interface</h1><br /><br />'
|
||||
|
||||
DWForm = '<h3>Dump your wallet:</h3><form style="margin-left:15px;" action="DumpWallet" method=get>\
|
||||
Wallet Directory: <input type=text name="dir" id="dwf-dir" size=40 /><br />\
|
||||
Wallet Filename: <input type=text name="name" id="dwf-name" /><br />\
|
||||
<input type=submit value="Dump wallet" onClick="document.getElementById(\'DWDiv\').style.display=\'block\';document.getElementById(\'dwf-close\').style.display=\'inline\';ajaxDW();return false;" />\
|
||||
<input type=button value="Close" onClick="document.getElementById(\'DWDiv\').style.display=\'none\';document.getElementById(\'dwf-close\').style.display=\'none\';" id="dwf-close" style="display:none;" />\
|
||||
<div id="DWDiv" style="display:none;margin:10px 3% 10px;padding:10px;overflow:auto;width:50%;max-height:300px;background-color:#fff8dd;"></div>\
|
||||
</form><br />'
|
||||
|
||||
InfoForm = '<h3>Get some info about one key:</h3><form style="margin-left:15px;" action="Info" method=get>\
|
||||
Key: <input type=text name="key" id="if-key" size=65 /><br />\
|
||||
<span style="border: 0 dashed;border-bottom-width:1px;" title="0 for Bitcoin, 52 for Namecoin, 111 for testnets">Version</span>: <input type=text name="vers" value=0 id="if-vers" size=1 /><br />\
|
||||
Format:<br />\
|
||||
<input type="radio" name="format" value="reg" CHECKED> Regular, base 58<br>\
|
||||
<input type="radio" name="format" value="hex" id="if-hex"> Hexadecimal, 64 characters long<br>\
|
||||
<input type=submit value="Get info" onClick="document.getElementById(\'InfoDiv\').style.display=\'block\';document.getElementById(\'if-close\').style.display=\'inline\';ajaxInfo();return false;" />\
|
||||
<input type=button value="Close" onClick="document.getElementById(\'InfoDiv\').style.display=\'none\';document.getElementById(\'if-close\').style.display=\'none\';" id="if-close" style="display:none;" />\
|
||||
<div id="InfoDiv" style="display:none;margin:10px 3% 10px;padding:10px;overflow:auto;width:50%;max-height:300px;background-color:#fff8dd;"></div>\
|
||||
</form><br />'
|
||||
|
||||
ImportForm = '<h3>Import a key in your wallet:</h3><form style="margin-left:15px;" action="Import" method=get>\
|
||||
Wallet Directory: <input type=text name="dir" id="impf-dir" size=40 /><br />\
|
||||
Wallet Filename: <input type=text name="name" id="impf-name" /><br />\
|
||||
Key: <input type=text name="key" id="impf-key" size=65 /><br />\
|
||||
Label: <input type=text name="label" id="impf-label" /><br />\
|
||||
<input type="checkbox" name="reserve" value="true" id="impf-reserve" onClick="document.getElementById(\'impf-label\').disabled=document.getElementById(\'impf-reserve\').checked" /> Reserve<br />\
|
||||
<span style="border: 0 dashed;border-bottom-width:1px;" title="0 for Bitcoin, 52 for Namecoin, 111 for testnets">Version</span>: <input type=text name="vers" value=0 id="impf-vers" size=1 /><br />\
|
||||
Format:<br />\
|
||||
<input type="radio" name="format" value="reg" CHECKED> Regular, base 58<br>\
|
||||
<input type="radio" name="format" value="hex" id="impf-hex" > Hexadecimal, 64 characters long<br>\
|
||||
<input type=submit value="Import key" onClick="document.getElementById(\'ImportDiv\').style.display=\'block\';document.getElementById(\'impf-close\').style.display=\'inline\';ajaxImport();return false;" />\
|
||||
<input type=button value="Close" onClick="document.getElementById(\'ImportDiv\').style.display=\'none\';document.getElementById(\'impf-close\').style.display=\'none\';" id="impf-close" style="display:none;" />\
|
||||
<div id="ImportDiv" style="display:none;margin:10px 3% 10px;padding:10px;overflow:auto;width:50%;max-height:300px;background-color:#fff8dd;"></div>\
|
||||
</form>'
|
||||
|
||||
Misc = ''
|
||||
|
||||
Javascript = '<script language="javascript" type="text/javascript">\
|
||||
function ajaxDW(){\
|
||||
var ajaxRequest;\
|
||||
try{\
|
||||
ajaxRequest = new XMLHttpRequest();\
|
||||
} catch (e){\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");\
|
||||
} catch (e) {\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");\
|
||||
} catch (e){\
|
||||
alert("Your browser broke!");\
|
||||
return false;\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
ajaxRequest.onreadystatechange = function(){\
|
||||
if(ajaxRequest.readyState == 4){\
|
||||
document.getElementById("DWDiv").innerHTML = ajaxRequest.responseText;\
|
||||
}\
|
||||
};\
|
||||
var queryString = "/DumpWallet?dir="+document.getElementById("dwf-dir").value+"&name="+document.getElementById("dwf-name").value;\
|
||||
ajaxRequest.open("GET", queryString, true);\
|
||||
document.getElementById("DWDiv").innerHTML = "Loading...";\
|
||||
ajaxRequest.send(null);\
|
||||
}\
|
||||
function ajaxInfo(){\
|
||||
var ajaxRequest;\
|
||||
try{\
|
||||
ajaxRequest = new XMLHttpRequest();\
|
||||
} catch (e){\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");\
|
||||
} catch (e) {\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");\
|
||||
} catch (e){\
|
||||
alert("Your browser broke!");\
|
||||
return false;\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
ajaxRequest.onreadystatechange = function(){\
|
||||
if(ajaxRequest.readyState == 4){\
|
||||
document.getElementById("InfoDiv").innerHTML = ajaxRequest.responseText;\
|
||||
}\
|
||||
};\
|
||||
var queryString = "/Info?key="+document.getElementById("if-key").value+"&vers="+document.getElementById("if-vers").value+"&format="+(document.getElementById("if-hex").checked?"hex":"reg");\
|
||||
ajaxRequest.open("GET", queryString, true);\
|
||||
document.getElementById("InfoDiv").innerHTML = "Loading...";\
|
||||
ajaxRequest.send(null);\
|
||||
}\
|
||||
function ajaxImport(){\
|
||||
var ajaxRequest;\
|
||||
try{\
|
||||
ajaxRequest = new XMLHttpRequest();\
|
||||
} catch (e){\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP");\
|
||||
} catch (e) {\
|
||||
try{\
|
||||
ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP");\
|
||||
} catch (e){\
|
||||
alert("Your browser broke!");\
|
||||
return false;\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
ajaxRequest.onreadystatechange = function(){\
|
||||
if(ajaxRequest.readyState == 4){\
|
||||
document.getElementById("ImportDiv").innerHTML = ajaxRequest.responseText;\
|
||||
}\
|
||||
};\
|
||||
var queryString = "/Import?dir="+document.getElementById("impf-dir").value+"&name="+document.getElementById("impf-name").value+"&key="+document.getElementById("impf-key").value+"&label="+document.getElementById("impf-label").value+"&vers="+document.getElementById("impf-vers").value+"&format="+(document.getElementById("impf-hex").checked?"hex":"reg")+(document.getElementById("impf-reserve").checked?"&reserve=1":"");\
|
||||
ajaxRequest.open("GET", queryString, true);\
|
||||
document.getElementById("ImportDiv").innerHTML = "Loading...";\
|
||||
ajaxRequest.send(null);\
|
||||
}\
|
||||
</script>'
|
||||
|
||||
page = '<html><head><title>Pywallet Web Interface</title></head><body>' + header + Javascript + DWForm + InfoForm + ImportForm + Misc + '</body></html>'
|
||||
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()
|
||||
|
||||
class WIDumpWallet(resource.Resource):
|
||||
|
||||
def render_GET(self, request):
|
||||
try:
|
||||
wdir=request.args['dir'][0]
|
||||
wname=request.args['name'][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)
|
||||
|
||||
read_wallet(json_db, create_env(wdir), wname, True, True, "", None)
|
||||
return 'Wallet: %s/%s<br />Dump:<pre>%s</pre>'%(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)
|
||||
|
||||
class WIInfo(resource.Resource):
|
||||
|
||||
def render_GET(self, request):
|
||||
global addrtype
|
||||
try:
|
||||
sec = request.args['key'][0]
|
||||
format = request.args['format'][0]
|
||||
addrtype = int(request.args['vers'][0])
|
||||
|
||||
if format in 'reg':
|
||||
pkey = regenerate_key(sec)
|
||||
elif len(sec) == 64:
|
||||
pkey = EC_KEY(str_to_long(sec.decode('hex')))
|
||||
else:
|
||||
return "Hexadecimal private keys must be 64 characters long"
|
||||
|
||||
if not pkey:
|
||||
return "Bad private key"
|
||||
|
||||
secret = GetSecret(pkey)
|
||||
private_key = GetPrivKey(pkey)
|
||||
public_key = GetPubKey(pkey)
|
||||
addr = public_key_to_bc_address(public_key)
|
||||
|
||||
return "Address (%s): %s<br />Privkey (%s): %s<br />Hexprivkey: %s" % ( aversions[addrtype], addr, aversions[addrtype], SecretToASecret(secret), secret.encode('hex') )
|
||||
|
||||
except:
|
||||
log.err()
|
||||
return 'Error in info page'
|
||||
|
||||
def render_POST(self, request):
|
||||
return self.render_GET(request)
|
||||
|
||||
|
||||
class WIImport(resource.Resource):
|
||||
|
||||
def render_GET(self, request):
|
||||
global addrtype
|
||||
try:
|
||||
sec = request.args['key'][0]
|
||||
format = request.args['format'][0]
|
||||
addrtype = int(request.args['vers'][0])
|
||||
wdir=request.args['dir'][0]
|
||||
wname=request.args['name'][0]
|
||||
reserve=request.args.has_key('reserve')
|
||||
label=request.args['label'][0]
|
||||
|
||||
if format in 'reg':
|
||||
pkey = regenerate_key(sec)
|
||||
elif len(sec) == 64:
|
||||
pkey = EC_KEY(str_to_long(sec.decode('hex')))
|
||||
else:
|
||||
return "Hexadecimal private keys must be 64 characters long"
|
||||
|
||||
if not pkey:
|
||||
return "Bad private key"
|
||||
|
||||
if not os.path.isfile(wdir+"/"+wname):
|
||||
return '%s/%s doesn\'t exist'%(wdir, wname)
|
||||
|
||||
|
||||
secret = GetSecret(pkey)
|
||||
private_key = GetPrivKey(pkey)
|
||||
public_key = GetPubKey(pkey)
|
||||
addr = public_key_to_bc_address(public_key)
|
||||
|
||||
db_env = create_env(wdir)
|
||||
read_wallet(json_db, db_env, wname, True, True, "", None)
|
||||
db = open_wallet(db_env, wname, writable=True)
|
||||
|
||||
if (format in 'reg' and sec in private_keys) or (format not in 'reg' and sec in private_hex_keys):
|
||||
return "Already exists"
|
||||
|
||||
update_wallet(db, 'key', { 'public_key' : public_key, 'private_key' : private_key })
|
||||
if not reserve:
|
||||
update_wallet(db, 'name', { 'hash' : addr, 'name' : label })
|
||||
|
||||
db.close()
|
||||
|
||||
return "<pre>Address: %s\nPrivkey: %s\nHexkey: %s\nKey imported in %s/%s<pre>" % (addr, SecretToASecret(secret), secret.encode('hex'), wdir, wname)
|
||||
|
||||
except:
|
||||
log.err()
|
||||
return 'Error in import page'
|
||||
|
||||
def render_POST(self, request):
|
||||
return self.render_GET(request)
|
||||
|
||||
class WI404(resource.Resource):
|
||||
|
||||
def render_GET(self, request):
|
||||
return 'Page Not Found'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = OptionParser(usage="%prog [options]", version="%prog 1.1")
|
||||
|
||||
@ -888,7 +1142,7 @@ def main():
|
||||
parser.add_option("--namecoin", dest="namecoin", action="store_true",
|
||||
help="use namecoin address type")
|
||||
|
||||
parser.add_option("--othernetwork", dest="otherversion",
|
||||
parser.add_option("--otherversion", dest="otherversion",
|
||||
help="use other network address type, whose version is OTHERVERSION")
|
||||
|
||||
parser.add_option("--info", dest="keyinfo", action="store_true",
|
||||
@ -900,8 +1154,34 @@ def main():
|
||||
parser.add_option("--balance", dest="key_balance",
|
||||
help="prints balance of KEY_BALANCE")
|
||||
|
||||
parser.add_option("--web", dest="web", action="store_true",
|
||||
help="run pywallet web interface")
|
||||
|
||||
parser.add_option("--port", dest="port",
|
||||
help="port of web interface (defaults to 8989)")
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
VIEWS = {
|
||||
'DumpWallet': WIDumpWallet(),
|
||||
'Import': WIImport(),
|
||||
'Info': WIInfo()
|
||||
}
|
||||
|
||||
if options.web is not None:
|
||||
webport = 8989
|
||||
if options.port is not None:
|
||||
webport = int(options.port)
|
||||
root = WIRoot()
|
||||
for viewName, className in VIEWS.items():
|
||||
root.putChild(viewName, className)
|
||||
log.startLogging(sys.stdout)
|
||||
log.msg('Starting server: %s' %str(datetime.now()))
|
||||
server = server.Site(root)
|
||||
reactor.listenTCP(webport, server)
|
||||
reactor.run()
|
||||
exit(0)
|
||||
|
||||
if options.key_balance is not None:
|
||||
print(balance(balance_site, options.key_balance))
|
||||
exit(0)
|
||||
@ -956,6 +1236,3 @@ def main():
|
||||
|
||||
db.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user