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 '') --label=LABEL label shown in the adress book (defaults to '')
--testnet use testnet subdirectory and address type --testnet use testnet subdirectory and address type
--namecoin use namecoin 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 --info display pubkey, privkey (both depending on the
network) and hexkey network) and hexkey
--reserve import as a reserve key, i.e. it won't show in the --reserve import as a reserve key, i.e. it won't show in the
adress book adress book
--balance=KEY_BALANCE --balance=KEY_BALANCE
prints balance of 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 random
import urllib 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 max_version = 32500
addrtype = 0 addrtype = 0
json_db = {} json_db = {}
@ -30,7 +37,7 @@ for i in range(256):
aversions[i] = "version %d" % i; aversions[i] = "version %d" % i;
aversions[0] = 'Bitcoin'; aversions[0] = 'Bitcoin';
aversions[52] = 'Namecoin'; aversions[52] = 'Namecoin';
aversions[111] = 'Bitcoin testnet'; aversions[111] = 'Testnet';
def determine_db_dir(): def determine_db_dir():
import os import os
@ -853,9 +860,256 @@ def keyinfo(sec, keyishex):
return True 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") parser = OptionParser(usage="%prog [options]", version="%prog 1.1")
@ -888,7 +1142,7 @@ def main():
parser.add_option("--namecoin", dest="namecoin", action="store_true", parser.add_option("--namecoin", dest="namecoin", action="store_true",
help="use namecoin address type") 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") help="use other network address type, whose version is OTHERVERSION")
parser.add_option("--info", dest="keyinfo", action="store_true", parser.add_option("--info", dest="keyinfo", action="store_true",
@ -900,8 +1154,34 @@ def main():
parser.add_option("--balance", dest="key_balance", parser.add_option("--balance", dest="key_balance",
help="prints balance of 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() (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: if options.key_balance is not None:
print(balance(balance_site, options.key_balance)) print(balance(balance_site, options.key_balance))
exit(0) exit(0)
@ -956,6 +1236,3 @@ def main():
db.close() db.close()
if __name__ == '__main__':
main()