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 '')
|
--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)
|
||||||
|
|
||||||
|
|||||||
291
pywallet.py
291
pywallet.py
@ -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()
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user