Add web interface

This commit is contained in:
jackjack 2011-08-03 17:35:39 +02:00
parent 1f9c16553f
commit ca80db790c
2 changed files with 289 additions and 7 deletions

5
README
View File

@ -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)

View File

@ -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()