Merge branch 'master' of gitorious.org:electrum/electrum
This commit is contained in:
commit
eb060854a1
@ -16,15 +16,22 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import re,sys
|
import re, sys, getpass
|
||||||
|
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
from wallet import Wallet
|
from wallet import Wallet
|
||||||
from interface import Interface
|
from interface import Interface
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# URL decode
|
||||||
|
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
||||||
|
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
||||||
|
|
||||||
|
|
||||||
|
from wallet import format_satoshis
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'payto', 'sendtx', 'password', 'newaddress', 'addresses', 'history', 'label', 'gui', 'mktx','seed','import']
|
known_commands = ['help', 'validateaddress', 'balance', 'contacts', 'create', 'payto', 'sendtx', 'password', 'newaddress', 'addresses', 'history', 'label', 'gui', 'mktx','seed','import','signmessage','verifymessage','eval']
|
||||||
|
|
||||||
usage = "usage: %prog [options] command args\nCommands: "+ (', '.join(known_commands))
|
usage = "usage: %prog [options] command args\nCommands: "+ (', '.join(known_commands))
|
||||||
|
|
||||||
@ -52,24 +59,30 @@ if __name__ == '__main__':
|
|||||||
import gui
|
import gui
|
||||||
gui.init_wallet(wallet)
|
gui.init_wallet(wallet)
|
||||||
gui = gui.BitcoinGUI(wallet)
|
gui = gui.BitcoinGUI(wallet)
|
||||||
|
|
||||||
if re.match('^bitcoin:', cmd):
|
if re.match('^bitcoin:', cmd):
|
||||||
|
|
||||||
o = cmd[8:].split('?')
|
o = cmd[8:].split('?')
|
||||||
address = o[0]
|
address = o[0]
|
||||||
if len(o)>1:
|
if len(o)>1:
|
||||||
params = o[1].split('&')
|
params = o[1].split('&')
|
||||||
else:
|
else:
|
||||||
params = []
|
params = []
|
||||||
cmd = 'gui'
|
|
||||||
amount = ''
|
amount = label = message = signature = identity = ''
|
||||||
label = ''
|
|
||||||
for p in params:
|
for p in params:
|
||||||
k,v = p.split('=')
|
k,v = p.split('=')
|
||||||
v = urldecode(v)
|
uv = urldecode(v)
|
||||||
if k=='amount': amount = v
|
if k == 'amount': amount = uv
|
||||||
elif k=='label': label = v
|
elif k == 'message': message = uv
|
||||||
else: print k,v
|
elif k == 'label': label = uv
|
||||||
|
elif k == 'signature':
|
||||||
gui.set_send_tab(address, amount, label)
|
identity, signature = uv.split(':')
|
||||||
|
cmd = cmd.replace('&%s=%s'%(k,v),'')
|
||||||
|
else:
|
||||||
|
print k,v
|
||||||
|
|
||||||
|
gui.set_send_tab(address, amount, message, label, identity, signature, cmd)
|
||||||
|
|
||||||
gui.main()
|
gui.main()
|
||||||
wallet.save()
|
wallet.save()
|
||||||
@ -98,7 +111,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
host = raw_input("server (default:%s):"%wallet.interface.host)
|
host = raw_input("server (default:%s):"%wallet.interface.host)
|
||||||
port = raw_input("port (default:%d):"%wallet.interface.port)
|
port = raw_input("port (default:%d):"%wallet.interface.port)
|
||||||
fee = raw_input("fee (default:%f):"%(wallet.fee*1e-8))
|
fee = raw_input("fee (default:%s):"%( str(Decimal(wallet.fee)/100000000)) )
|
||||||
if fee: wallet.fee = float(fee)
|
if fee: wallet.fee = float(fee)
|
||||||
if host: wallet.interface.host = host
|
if host: wallet.interface.host = host
|
||||||
if port: wallet.interface.port = int(port)
|
if port: wallet.interface.port = int(port)
|
||||||
@ -136,13 +149,13 @@ if __name__ == '__main__':
|
|||||||
cmd = 'help'
|
cmd = 'help'
|
||||||
|
|
||||||
# open session
|
# open session
|
||||||
if cmd not in ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress']:
|
if cmd not in ['password', 'mktx', 'history', 'label', 'contacts', 'help', 'validateaddress', 'signmessage', 'verifymessage', 'eval']:
|
||||||
wallet.interface.new_session(wallet.all_addresses(), wallet.electrum_version)
|
wallet.interface.new_session(wallet.all_addresses(), wallet.electrum_version)
|
||||||
wallet.update()
|
wallet.update()
|
||||||
wallet.save()
|
wallet.save()
|
||||||
|
|
||||||
# commands needing password
|
# commands needing password
|
||||||
if cmd in ['payto', 'password', 'mktx', 'seed', 'import' ] or ( cmd=='addresses' and options.show_keys):
|
if cmd in ['payto', 'password', 'mktx', 'seed', 'import','signmessage' ] or ( cmd=='addresses' and options.show_keys):
|
||||||
password = getpass.getpass('Password:') if wallet.use_encryption else None
|
password = getpass.getpass('Password:') if wallet.use_encryption else None
|
||||||
# check password
|
# check password
|
||||||
try:
|
try:
|
||||||
@ -193,6 +206,8 @@ if __name__ == '__main__':
|
|||||||
print "syntax: mktx <recipient> <amount> [label]"
|
print "syntax: mktx <recipient> <amount> [label]"
|
||||||
elif cmd2 == 'seed':
|
elif cmd2 == 'seed':
|
||||||
print "show generation seed of your wallet. password protected."
|
print "show generation seed of your wallet. password protected."
|
||||||
|
elif cmd2 == 'eval':
|
||||||
|
print "run python eval on an object"
|
||||||
|
|
||||||
elif cmd == 'seed':
|
elif cmd == 'seed':
|
||||||
import mnemonic
|
import mnemonic
|
||||||
@ -211,21 +226,25 @@ if __name__ == '__main__':
|
|||||||
if addrs == []:
|
if addrs == []:
|
||||||
c, u = wallet.get_balance()
|
c, u = wallet.get_balance()
|
||||||
if u:
|
if u:
|
||||||
print c*1e-8, u*1e-8
|
print Decimal( c ) / 100000000 , Decimal( u ) / 100000000
|
||||||
else:
|
else:
|
||||||
print c*1e-8
|
print Decimal( c ) / 100000000
|
||||||
else:
|
else:
|
||||||
for addr in addrs:
|
for addr in addrs:
|
||||||
c, u = wallet.get_addr_balance(addr)
|
c, u = wallet.get_addr_balance(addr)
|
||||||
if u:
|
if u:
|
||||||
print "%s %s, %s" % (addr, c*1e-8, u*1e-8)
|
print "%s %s, %s" % (addr, str(Decimal(c)/100000000), str(Decimal(u)/100000000))
|
||||||
else:
|
else:
|
||||||
print "%s %s" % (addr, c*1e-8)
|
print "%s %s" % (addr, str(Decimal(c)/100000000))
|
||||||
|
|
||||||
elif cmd in [ 'contacts']:
|
elif cmd in [ 'contacts']:
|
||||||
for addr in wallet.addressbook:
|
for addr in wallet.addressbook:
|
||||||
print addr, " ", wallet.labels.get(addr)
|
print addr, " ", wallet.labels.get(addr)
|
||||||
|
|
||||||
|
elif cmd == 'eval':
|
||||||
|
print eval(args[1])
|
||||||
|
wallet.save()
|
||||||
|
|
||||||
elif cmd in [ 'addresses']:
|
elif cmd in [ 'addresses']:
|
||||||
for addr in wallet.all_addresses():
|
for addr in wallet.all_addresses():
|
||||||
if options.show_all or not wallet.is_change(addr):
|
if options.show_all or not wallet.is_change(addr):
|
||||||
@ -240,7 +259,7 @@ if __name__ == '__main__':
|
|||||||
for item in h:
|
for item in h:
|
||||||
if item['is_in']: ni += 1
|
if item['is_in']: ni += 1
|
||||||
else: no += 1
|
else: no += 1
|
||||||
b = "%d %d %f"%(no, ni, wallet.get_addr_balance(addr)[0]*1e-8)
|
b = "%d %d %s"%(no, ni, str(Decimal(wallet.get_addr_balance(addr)[0])/100000000))
|
||||||
else: b=''
|
else: b=''
|
||||||
if options.show_keys:
|
if options.show_keys:
|
||||||
pk = wallet.get_private_key2(addr, password)
|
pk = wallet.get_private_key2(addr, password)
|
||||||
@ -252,9 +271,8 @@ if __name__ == '__main__':
|
|||||||
b = 0
|
b = 0
|
||||||
for line in lines:
|
for line in lines:
|
||||||
import datetime
|
import datetime
|
||||||
v = 1.*line['value']/1e8
|
v = line['value']
|
||||||
b += v
|
b += v
|
||||||
v_str = "%f"%v if v<0 else "+%f"%v
|
|
||||||
try:
|
try:
|
||||||
time_str = datetime.datetime.fromtimestamp( line['nTime'])
|
time_str = datetime.datetime.fromtimestamp( line['nTime'])
|
||||||
except:
|
except:
|
||||||
@ -264,8 +282,8 @@ if __name__ == '__main__':
|
|||||||
if not label: label = line['tx_hash']
|
if not label: label = line['tx_hash']
|
||||||
else: label = label + ' '*(64 - len(label) )
|
else: label = label + ' '*(64 - len(label) )
|
||||||
|
|
||||||
print time_str, " ", label, " ", v_str, " ", "%f"%b
|
print time_str , " " + label + " " + format_satoshis(v)+ " "+ format_satoshis(b)
|
||||||
print "# balance: ", b
|
print "# balance: ", format_satoshis(b)
|
||||||
|
|
||||||
elif cmd == 'label':
|
elif cmd == 'label':
|
||||||
try:
|
try:
|
||||||
@ -323,3 +341,16 @@ if __name__ == '__main__':
|
|||||||
else:
|
else:
|
||||||
print "error: mismatch"
|
print "error: mismatch"
|
||||||
|
|
||||||
|
elif cmd == 'signmessage':
|
||||||
|
address, message = args[1:3]
|
||||||
|
print wallet.sign_message(address, message, password)
|
||||||
|
|
||||||
|
elif cmd == 'verifymessage':
|
||||||
|
address, signature, message = args[1:4]
|
||||||
|
try:
|
||||||
|
wallet.verify_message(address, signature, message)
|
||||||
|
print True
|
||||||
|
except:
|
||||||
|
print False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
324
client/gui.py
324
client/gui.py
@ -17,7 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import thread, time, ast, sys
|
import thread, time, ast, sys, re
|
||||||
import socket, traceback
|
import socket, traceback
|
||||||
import pygtk
|
import pygtk
|
||||||
pygtk.require('2.0')
|
pygtk.require('2.0')
|
||||||
@ -28,12 +28,7 @@ from decimal import Decimal
|
|||||||
gtk.gdk.threads_init()
|
gtk.gdk.threads_init()
|
||||||
APP_NAME = "Electrum"
|
APP_NAME = "Electrum"
|
||||||
|
|
||||||
def format_satoshis(x):
|
from wallet import format_satoshis
|
||||||
s = str( Decimal(x) /100000000 )
|
|
||||||
if not '.' in s: s += '.'
|
|
||||||
p = s.find('.')
|
|
||||||
s += " "*( 9 - ( len(s) - p ))
|
|
||||||
return s
|
|
||||||
|
|
||||||
def numbify(entry, is_int = False):
|
def numbify(entry, is_int = False):
|
||||||
text = entry.get_text().strip()
|
text = entry.get_text().strip()
|
||||||
@ -124,6 +119,7 @@ def init_wallet(wallet):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# ask for the server.
|
# ask for the server.
|
||||||
|
wallet.interface.get_servers()
|
||||||
run_network_dialog( wallet, parent=None )
|
run_network_dialog( wallet, parent=None )
|
||||||
|
|
||||||
# ask for seed and gap.
|
# ask for seed and gap.
|
||||||
@ -370,8 +366,9 @@ def run_network_dialog( wallet, parent ):
|
|||||||
if host!= wallet.interface.host or port!=wallet.interface.port:
|
if host!= wallet.interface.host or port!=wallet.interface.port:
|
||||||
wallet.interface.host = host
|
wallet.interface.host = host
|
||||||
wallet.interface.set_port( port )
|
wallet.interface.set_port( port )
|
||||||
wallet.save()
|
|
||||||
wallet.interface.is_connected = False
|
wallet.interface.is_connected = False
|
||||||
|
if parent:
|
||||||
|
wallet.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -399,8 +396,8 @@ def password_line(label):
|
|||||||
password.show()
|
password.show()
|
||||||
return password, password_entry
|
return password, password_entry
|
||||||
|
|
||||||
def password_dialog():
|
def password_dialog(parent):
|
||||||
dialog = gtk.MessageDialog( None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
dialog = gtk.MessageDialog( parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||||
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
|
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, "Please enter your password.")
|
||||||
dialog.get_image().set_visible(False)
|
dialog.get_image().set_visible(False)
|
||||||
current_pw, current_pw_entry = password_line('Password:')
|
current_pw, current_pw_entry = password_line('Password:')
|
||||||
@ -529,7 +526,7 @@ class BitcoinGUI:
|
|||||||
|
|
||||||
def seedb(w, wallet):
|
def seedb(w, wallet):
|
||||||
if wallet.use_encryption:
|
if wallet.use_encryption:
|
||||||
password = password_dialog()
|
password = password_dialog(self.window)
|
||||||
if not password: return
|
if not password: return
|
||||||
else: password = None
|
else: password = None
|
||||||
show_seed_dialog(wallet, password, self.window)
|
show_seed_dialog(wallet, password, self.window)
|
||||||
@ -568,7 +565,7 @@ class BitcoinGUI:
|
|||||||
|
|
||||||
self.window.add(vbox)
|
self.window.add(vbox)
|
||||||
self.window.show_all()
|
self.window.show_all()
|
||||||
self.fee_box.hide()
|
#self.fee_box.hide()
|
||||||
|
|
||||||
self.context_id = self.status_bar.get_context_id("statusbar")
|
self.context_id = self.status_bar.get_context_id("statusbar")
|
||||||
self.update_status_bar()
|
self.update_status_bar()
|
||||||
@ -578,6 +575,27 @@ class BitcoinGUI:
|
|||||||
gobject.idle_add( self.update_status_bar )
|
gobject.idle_add( self.update_status_bar )
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
|
||||||
|
def check_recipient_thread():
|
||||||
|
old_r = ''
|
||||||
|
while True:
|
||||||
|
time.sleep(0.5)
|
||||||
|
if self.payto_entry.is_focus():
|
||||||
|
continue
|
||||||
|
r = self.payto_entry.get_text()
|
||||||
|
if r != old_r:
|
||||||
|
old_r = r
|
||||||
|
r = r.strip()
|
||||||
|
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r):
|
||||||
|
try:
|
||||||
|
to_address = self.get_alias(r, interactive=False)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if to_address:
|
||||||
|
s = r + ' <' + to_address + '>'
|
||||||
|
gobject.idle_add( lambda: self.payto_entry.set_text(s) )
|
||||||
|
|
||||||
|
|
||||||
def update_wallet_thread():
|
def update_wallet_thread():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -625,6 +643,7 @@ class BitcoinGUI:
|
|||||||
|
|
||||||
thread.start_new_thread(update_wallet_thread, ())
|
thread.start_new_thread(update_wallet_thread, ())
|
||||||
thread.start_new_thread(update_status_bar_thread, ())
|
thread.start_new_thread(update_status_bar_thread, ())
|
||||||
|
thread.start_new_thread(check_recipient_thread, ())
|
||||||
self.notebook.set_current_page(0)
|
self.notebook.set_current_page(0)
|
||||||
|
|
||||||
|
|
||||||
@ -635,60 +654,68 @@ class BitcoinGUI:
|
|||||||
|
|
||||||
|
|
||||||
def create_send_tab(self):
|
def create_send_tab(self):
|
||||||
|
|
||||||
page = vbox = gtk.VBox()
|
page = vbox = gtk.VBox()
|
||||||
page.show()
|
page.show()
|
||||||
|
|
||||||
payto = gtk.HBox()
|
payto = gtk.HBox()
|
||||||
payto_label = gtk.Label('Pay to:')
|
payto_label = gtk.Label('Pay to:')
|
||||||
payto_label.set_size_request(100,10)
|
payto_label.set_size_request(100,-1)
|
||||||
payto_label.show()
|
|
||||||
payto.pack_start(payto_label, False)
|
payto.pack_start(payto_label, False)
|
||||||
payto_entry = gtk.Entry()
|
payto_entry = gtk.Entry()
|
||||||
payto_entry.set_size_request(350, 26)
|
payto_entry.set_size_request(450, 26)
|
||||||
payto_entry.show()
|
|
||||||
payto.pack_start(payto_entry, False)
|
payto.pack_start(payto_entry, False)
|
||||||
vbox.pack_start(payto, False, False, 5)
|
vbox.pack_start(payto, False, False, 5)
|
||||||
|
|
||||||
label = gtk.HBox()
|
message = gtk.HBox()
|
||||||
label_label = gtk.Label('Label:')
|
message_label = gtk.Label('Description:')
|
||||||
label_label.set_size_request(100,10)
|
message_label.set_size_request(100,-1)
|
||||||
label_label.show()
|
message.pack_start(message_label, False)
|
||||||
label.pack_start(label_label, False)
|
message_entry = gtk.Entry()
|
||||||
label_entry = gtk.Entry()
|
message_entry.set_size_request(450, 26)
|
||||||
label_entry.set_size_request(350, 26)
|
message.pack_start(message_entry, False)
|
||||||
label_entry.show()
|
vbox.pack_start(message, False, False, 5)
|
||||||
label.pack_start(label_entry, False)
|
|
||||||
vbox.pack_start(label, False, False, 5)
|
|
||||||
|
|
||||||
amount_box = gtk.HBox()
|
amount_box = gtk.HBox()
|
||||||
amount_label = gtk.Label('Amount:')
|
amount_label = gtk.Label('Amount:')
|
||||||
amount_label.set_size_request(100,-1)
|
amount_label.set_size_request(100,-1)
|
||||||
amount_label.show()
|
|
||||||
amount_box.pack_start(amount_label, False)
|
amount_box.pack_start(amount_label, False)
|
||||||
amount_entry = gtk.Entry()
|
amount_entry = gtk.Entry()
|
||||||
amount_entry.set_size_request(120, -1)
|
amount_entry.set_size_request(120, -1)
|
||||||
amount_entry.show()
|
|
||||||
amount_box.pack_start(amount_entry, False)
|
amount_box.pack_start(amount_entry, False)
|
||||||
vbox.pack_start(amount_box, False, False, 5)
|
vbox.pack_start(amount_box, False, False, 5)
|
||||||
|
|
||||||
send_button = gtk.Button("Send")
|
|
||||||
send_button.show()
|
|
||||||
amount_box.pack_start(send_button, False, False, 5)
|
|
||||||
|
|
||||||
self.fee_box = fee_box = gtk.HBox()
|
self.fee_box = fee_box = gtk.HBox()
|
||||||
fee_label = gtk.Label('Fee:')
|
fee_label = gtk.Label('Fee:')
|
||||||
fee_label.set_size_request(100,10)
|
fee_label.set_size_request(100,-1)
|
||||||
fee_box.pack_start(fee_label, False)
|
fee_box.pack_start(fee_label, False)
|
||||||
fee_entry = gtk.Entry()
|
fee_entry = gtk.Entry()
|
||||||
fee_entry.set_size_request(120, 26)
|
fee_entry.set_size_request(60, 26)
|
||||||
fee_entry.set_has_frame(False)
|
|
||||||
fee_entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
|
|
||||||
fee_box.pack_start(fee_entry, False)
|
fee_box.pack_start(fee_entry, False)
|
||||||
|
|
||||||
send_button.connect("clicked", self.do_send, (payto_entry, label_entry, amount_entry, fee_entry))
|
|
||||||
vbox.pack_start(fee_box, False, False, 5)
|
vbox.pack_start(fee_box, False, False, 5)
|
||||||
|
|
||||||
|
end_box = gtk.HBox()
|
||||||
|
empty_label = gtk.Label('')
|
||||||
|
empty_label.set_size_request(100,-1)
|
||||||
|
end_box.pack_start(empty_label, False)
|
||||||
|
send_button = gtk.Button("Send")
|
||||||
|
send_button.show()
|
||||||
|
end_box.pack_start(send_button, False, False, 0)
|
||||||
|
clear_button = gtk.Button("Clear")
|
||||||
|
clear_button.show()
|
||||||
|
end_box.pack_start(clear_button, False, False, 15)
|
||||||
|
send_button.connect("clicked", self.do_send, (payto_entry, message_entry, amount_entry, fee_entry))
|
||||||
|
clear_button.connect("clicked", self.do_clear, (payto_entry, message_entry, amount_entry, fee_entry))
|
||||||
|
|
||||||
|
vbox.pack_start(end_box, False, False, 5)
|
||||||
|
|
||||||
|
# display this line only if there is a signature
|
||||||
|
payto_sig = gtk.HBox()
|
||||||
|
payto_sig_id = gtk.Label('')
|
||||||
|
payto_sig.pack_start(payto_sig_id, False)
|
||||||
|
vbox.pack_start(payto_sig, True, True, 5)
|
||||||
|
|
||||||
|
|
||||||
self.user_fee = False
|
self.user_fee = False
|
||||||
|
|
||||||
def entry_changed( entry, is_fee ):
|
def entry_changed( entry, is_fee ):
|
||||||
@ -696,7 +723,8 @@ class BitcoinGUI:
|
|||||||
fee = numbify(fee_entry)
|
fee = numbify(fee_entry)
|
||||||
if not is_fee: fee = None
|
if not is_fee: fee = None
|
||||||
if amount is None:
|
if amount is None:
|
||||||
self.fee_box.hide(); return
|
#self.fee_box.hide();
|
||||||
|
return
|
||||||
inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
|
inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
|
||||||
if not is_fee:
|
if not is_fee:
|
||||||
fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
|
fee_entry.set_text( str( Decimal( fee ) / 100000000 ) )
|
||||||
@ -713,25 +741,68 @@ class BitcoinGUI:
|
|||||||
self.error = 'Not enough funds'
|
self.error = 'Not enough funds'
|
||||||
|
|
||||||
amount_entry.connect('changed', entry_changed, False)
|
amount_entry.connect('changed', entry_changed, False)
|
||||||
fee_entry.connect('changed', entry_changed, True)
|
fee_entry.connect('changed', entry_changed, True)
|
||||||
|
|
||||||
self.payto_entry = payto_entry
|
self.payto_entry = payto_entry
|
||||||
self.payto_amount_entry = amount_entry
|
self.payto_fee_entry = fee_entry
|
||||||
self.payto_label_entry = label_entry
|
self.payto_sig_id = payto_sig_id
|
||||||
|
self.payto_sig = payto_sig
|
||||||
|
self.amount_entry = amount_entry
|
||||||
|
self.message_entry = message_entry
|
||||||
self.add_tab(page, 'Send')
|
self.add_tab(page, 'Send')
|
||||||
|
|
||||||
def set_send_tab(self, address, amount, label):
|
def set_frozen(self,entry,frozen):
|
||||||
|
if frozen:
|
||||||
|
entry.set_editable(False)
|
||||||
|
entry.set_has_frame(False)
|
||||||
|
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#eeeeee"))
|
||||||
|
else:
|
||||||
|
entry.set_editable(True)
|
||||||
|
entry.set_has_frame(True)
|
||||||
|
entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#ffffff"))
|
||||||
|
|
||||||
|
|
||||||
|
def set_send_tab(self, payto, amount, message, label, identity, signature, cmd):
|
||||||
self.notebook.set_current_page(1)
|
self.notebook.set_current_page(1)
|
||||||
self.payto_entry.set_text(address)
|
|
||||||
self.payto_label_entry.set_text(label)
|
if signature:
|
||||||
self.payto_amount_entry.set_text(amount)
|
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', identity):
|
||||||
|
signing_address = self.get_alias(identity, interactive = True)
|
||||||
|
elif self.wallet.is_valid(identity):
|
||||||
|
signing_address = identity
|
||||||
|
else:
|
||||||
|
signing_address = None
|
||||||
|
if not signing_address:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.wallet.verify_message(signing_address, signature, cmd )
|
||||||
|
self.wallet.receipt = (signing_address, signature, cmd)
|
||||||
|
except:
|
||||||
|
self.show_message('Warning: the URI contains a bad signature.\nThe identity of the recipient cannot be verified.')
|
||||||
|
payto = amount = label = identity = message = ''
|
||||||
|
|
||||||
|
# redundant with aliases
|
||||||
|
#if label and payto:
|
||||||
|
# self.labels[payto] = label
|
||||||
|
if re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', payto):
|
||||||
|
payto_address = self.get_alias(payto, interactive=True)
|
||||||
|
if payto_address:
|
||||||
|
payto = payto + ' <' + payto_address + '>'
|
||||||
|
|
||||||
|
self.payto_entry.set_text(payto)
|
||||||
|
self.message_entry.set_text(message)
|
||||||
|
self.amount_entry.set_text(amount)
|
||||||
|
if identity:
|
||||||
|
self.set_frozen(self.payto_entry,True)
|
||||||
|
self.set_frozen(self.amount_entry,True)
|
||||||
|
self.set_frozen(self.message_entry,True)
|
||||||
|
self.payto_sig_id.set_text( ' The bitcoin URI was signed by ' + identity )
|
||||||
|
else:
|
||||||
|
self.payto_sig.set_visible(False)
|
||||||
|
|
||||||
def create_about_tab(self):
|
def create_about_tab(self):
|
||||||
page = gtk.VBox()
|
page = gtk.VBox()
|
||||||
page.show()
|
page.show()
|
||||||
#self.info = gtk.Label('')
|
|
||||||
#self.info.set_selectable(True)
|
|
||||||
#page.pack_start(self.info)
|
|
||||||
tv = gtk.TextView()
|
tv = gtk.TextView()
|
||||||
tv.set_editable(False)
|
tv.set_editable(False)
|
||||||
tv.set_cursor_visible(False)
|
tv.set_cursor_visible(False)
|
||||||
@ -739,14 +810,84 @@ class BitcoinGUI:
|
|||||||
self.info = tv.get_buffer()
|
self.info = tv.get_buffer()
|
||||||
self.add_tab(page, 'Wall')
|
self.add_tab(page, 'Wall')
|
||||||
|
|
||||||
|
def do_clear(self, w, data):
|
||||||
|
self.payto_sig.set_visible(False)
|
||||||
|
self.payto_fee_entry.set_text('')
|
||||||
|
for entry in [self.payto_entry,self.amount_entry,self.message_entry]:
|
||||||
|
self.set_frozen(entry,False)
|
||||||
|
entry.set_text('')
|
||||||
|
|
||||||
|
|
||||||
|
def question(self,msg):
|
||||||
|
dialog = gtk.MessageDialog( self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, msg)
|
||||||
|
dialog.show()
|
||||||
|
result = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
return result == gtk.RESPONSE_OK
|
||||||
|
|
||||||
|
def get_alias(self, alias, interactive = False):
|
||||||
|
try:
|
||||||
|
target, signing_address, auth_name = self.wallet.read_alias(alias)
|
||||||
|
except BaseException, e:
|
||||||
|
# raise exception if verify fails (verify the chain)
|
||||||
|
if interactive:
|
||||||
|
self.show_message("Alias error: " + e.message)
|
||||||
|
return
|
||||||
|
|
||||||
|
print target, signing_address, auth_name
|
||||||
|
|
||||||
|
if auth_name is None:
|
||||||
|
a = self.wallet.aliases.get(alias)
|
||||||
|
if not a:
|
||||||
|
msg = "Warning: the alias '%s' is self-signed. The signing address is %s. Do you want to trust this alias?"%(alias,signing_address)
|
||||||
|
if interactive and self.question( msg ):
|
||||||
|
self.wallet.aliases[alias] = (signing_address, target)
|
||||||
|
else:
|
||||||
|
target = None
|
||||||
|
else:
|
||||||
|
if signing_address != a[0]:
|
||||||
|
msg = "Warning: the key of alias '%s' has changed since your last visit! It is possible that someone is trying to do something nasty!!!\nDo you accept to change your trusted key?"%alias
|
||||||
|
if interactive and self.question( msg ):
|
||||||
|
self.wallet.aliases[alias] = (signing_address, target)
|
||||||
|
else:
|
||||||
|
target = None
|
||||||
|
else:
|
||||||
|
if signing_address not in self.wallet.authorities.keys():
|
||||||
|
msg = "The alias: '%s' links to %s\n\nWarning: this alias was signed by an unknown key.\nSigning authority: %s\nSigning address: %s\n\nDo you want to add this key to your list of trusted keys?"%(alias,target,auth_name,signing_address)
|
||||||
|
if interactive and self.question( msg ):
|
||||||
|
self.wallet.authorities[signing_address] = auth_name
|
||||||
|
else:
|
||||||
|
target = None
|
||||||
|
|
||||||
|
if target:
|
||||||
|
self.wallet.aliases[alias] = (signing_address, target)
|
||||||
|
self.update_sending_tab()
|
||||||
|
|
||||||
|
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def do_send(self, w, data):
|
def do_send(self, w, data):
|
||||||
payto_entry, label_entry, amount_entry, fee_entry = data
|
payto_entry, label_entry, amount_entry, fee_entry = data
|
||||||
|
|
||||||
label = label_entry.get_text()
|
label = label_entry.get_text()
|
||||||
|
r = payto_entry.get_text()
|
||||||
|
r = r.strip()
|
||||||
|
|
||||||
|
m1 = re.match('^(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+)$', r)
|
||||||
|
m2 = re.match('(|([\w\-\.]+)@)((\w[\w\-]+\.)+[\w\-]+) \<([1-9A-HJ-NP-Za-km-z]{26,})\>', r)
|
||||||
|
|
||||||
|
if m1:
|
||||||
|
to_address = self.get_alias(r, interactive = True)
|
||||||
|
if not to_address:
|
||||||
|
return
|
||||||
|
elif m2:
|
||||||
|
to_address = m2.group(5)
|
||||||
|
else:
|
||||||
|
to_address = r
|
||||||
|
|
||||||
to_address = payto_entry.get_text()
|
|
||||||
if not self.wallet.is_valid(to_address):
|
if not self.wallet.is_valid(to_address):
|
||||||
self.show_message( "invalid bitcoin address")
|
self.show_message( "invalid bitcoin address:\n"+to_address)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -761,7 +902,7 @@ class BitcoinGUI:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if self.wallet.use_encryption:
|
if self.wallet.use_encryption:
|
||||||
password = password_dialog()
|
password = password_dialog(self.window)
|
||||||
if not password:
|
if not password:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
@ -782,7 +923,7 @@ class BitcoinGUI:
|
|||||||
label_entry.set_text("")
|
label_entry.set_text("")
|
||||||
amount_entry.set_text("")
|
amount_entry.set_text("")
|
||||||
fee_entry.set_text("")
|
fee_entry.set_text("")
|
||||||
self.fee_box.hide()
|
#self.fee_box.hide()
|
||||||
self.update_sending_tab()
|
self.update_sending_tab()
|
||||||
else:
|
else:
|
||||||
self.show_message( msg )
|
self.show_message( msg )
|
||||||
@ -791,8 +932,20 @@ class BitcoinGUI:
|
|||||||
def treeview_button_press(self, treeview, event):
|
def treeview_button_press(self, treeview, event):
|
||||||
if event.type == gtk.gdk._2BUTTON_PRESS:
|
if event.type == gtk.gdk._2BUTTON_PRESS:
|
||||||
c = treeview.get_cursor()[0]
|
c = treeview.get_cursor()[0]
|
||||||
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
if treeview == self.history_treeview:
|
||||||
self.show_message(tx_details)
|
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
||||||
|
self.show_message(tx_details)
|
||||||
|
elif treeview == self.contacts_treeview:
|
||||||
|
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||||||
|
a = self.wallet.aliases.get(m)
|
||||||
|
if a:
|
||||||
|
if a[0] in self.wallet.authorities.keys():
|
||||||
|
s = self.wallet.authorities.get(a[0])
|
||||||
|
else:
|
||||||
|
s = "self"
|
||||||
|
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||||
|
self.show_message(msg)
|
||||||
|
|
||||||
|
|
||||||
def treeview_key_press(self, treeview, event):
|
def treeview_key_press(self, treeview, event):
|
||||||
c = treeview.get_cursor()[0]
|
c = treeview.get_cursor()[0]
|
||||||
@ -800,9 +953,21 @@ class BitcoinGUI:
|
|||||||
if c and c[0] == 0:
|
if c and c[0] == 0:
|
||||||
treeview.parent.grab_focus()
|
treeview.parent.grab_focus()
|
||||||
treeview.set_cursor((0,))
|
treeview.set_cursor((0,))
|
||||||
elif event.keyval == gtk.keysyms.Return and treeview == self.history_treeview:
|
elif event.keyval == gtk.keysyms.Return:
|
||||||
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
if treeview == self.history_treeview:
|
||||||
self.show_message(tx_details)
|
tx_details = self.history_list.get_value( self.history_list.get_iter(c), 8)
|
||||||
|
self.show_message(tx_details)
|
||||||
|
elif treeview == self.contacts_treeview:
|
||||||
|
m = self.addressbook_list.get_value( self.addressbook_list.get_iter(c), 0)
|
||||||
|
a = self.wallet.aliases.get(m)
|
||||||
|
if a:
|
||||||
|
if a[0] in self.wallet.authorities.keys():
|
||||||
|
s = self.wallet.authorities.get(a[0])
|
||||||
|
else:
|
||||||
|
s = "self"
|
||||||
|
msg = 'Alias:'+ m + '\n\nTarget: '+ a[1] + '\nSigned by: ' + s + '\nSigning address:' + a[0]
|
||||||
|
self.show_message(msg)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_history_tab(self):
|
def create_history_tab(self):
|
||||||
@ -827,7 +992,7 @@ class BitcoinGUI:
|
|||||||
tvcolumn.pack_start(cell, False)
|
tvcolumn.pack_start(cell, False)
|
||||||
tvcolumn.add_attribute(cell, 'text', 2)
|
tvcolumn.add_attribute(cell, 'text', 2)
|
||||||
|
|
||||||
tvcolumn = gtk.TreeViewColumn('Label')
|
tvcolumn = gtk.TreeViewColumn('Description')
|
||||||
treeview.append_column(tvcolumn)
|
treeview.append_column(tvcolumn)
|
||||||
cell = gtk.CellRendererText()
|
cell = gtk.CellRendererText()
|
||||||
cell.set_property('foreground', 'grey')
|
cell.set_property('foreground', 'grey')
|
||||||
@ -892,7 +1057,10 @@ class BitcoinGUI:
|
|||||||
liststore = self.recv_list if is_recv else self.addressbook_list
|
liststore = self.recv_list if is_recv else self.addressbook_list
|
||||||
treeview = gtk.TreeView(model= liststore)
|
treeview = gtk.TreeView(model= liststore)
|
||||||
treeview.connect('key-press-event', self.treeview_key_press)
|
treeview.connect('key-press-event', self.treeview_key_press)
|
||||||
|
treeview.connect('button-press-event', self.treeview_button_press)
|
||||||
treeview.show()
|
treeview.show()
|
||||||
|
if not is_recv:
|
||||||
|
self.contacts_treeview = treeview
|
||||||
|
|
||||||
tvcolumn = gtk.TreeViewColumn('Address')
|
tvcolumn = gtk.TreeViewColumn('Address')
|
||||||
treeview.append_column(tvcolumn)
|
treeview.append_column(tvcolumn)
|
||||||
@ -993,7 +1161,7 @@ class BitcoinGUI:
|
|||||||
address = liststore.get_value( liststore.get_iter(path), 0)
|
address = liststore.get_value( liststore.get_iter(path), 0)
|
||||||
self.payto_entry.set_text( address )
|
self.payto_entry.set_text( address )
|
||||||
self.notebook.set_current_page(1)
|
self.notebook.set_current_page(1)
|
||||||
self.payto_amount_entry.grab_focus()
|
self.amount_entry.grab_focus()
|
||||||
|
|
||||||
button.connect("clicked", payto, treeview, liststore)
|
button.connect("clicked", payto, treeview, liststore)
|
||||||
button.show()
|
button.show()
|
||||||
@ -1013,7 +1181,7 @@ class BitcoinGUI:
|
|||||||
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
self.status_image.set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU)
|
||||||
self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.interface.host, self.wallet.interface.blocks))
|
self.network_button.set_tooltip_text("Trying to contact %s.\n%d blocks"%(self.wallet.interface.host, self.wallet.interface.blocks))
|
||||||
text = "Balance: %s "%( format_satoshis(c) )
|
text = "Balance: %s "%( format_satoshis(c) )
|
||||||
if u: text += "[+ %s unconfirmed]"%( format_satoshis(u) )
|
if u: text += "[%s unconfirmed]"%( format_satoshis(u,True) )
|
||||||
if self.error: text = self.error
|
if self.error: text = self.error
|
||||||
self.status_bar.pop(self.context_id)
|
self.status_bar.pop(self.context_id)
|
||||||
self.status_bar.push(self.context_id, text)
|
self.status_bar.push(self.context_id, text)
|
||||||
@ -1033,6 +1201,11 @@ class BitcoinGUI:
|
|||||||
def update_sending_tab(self):
|
def update_sending_tab(self):
|
||||||
# detect addresses that are not mine in history, add them here...
|
# detect addresses that are not mine in history, add them here...
|
||||||
self.addressbook_list.clear()
|
self.addressbook_list.clear()
|
||||||
|
for alias, v in self.wallet.aliases.items():
|
||||||
|
s, target = v
|
||||||
|
label = self.wallet.labels.get(alias)
|
||||||
|
self.addressbook_list.append((alias, label, '-'))
|
||||||
|
|
||||||
for address in self.wallet.addressbook:
|
for address in self.wallet.addressbook:
|
||||||
label = self.wallet.labels.get(address)
|
label = self.wallet.labels.get(address)
|
||||||
n = 0
|
n = 0
|
||||||
@ -1062,16 +1235,23 @@ class BitcoinGUI:
|
|||||||
if is_default_label: label = tx['default_label']
|
if is_default_label: label = tx['default_label']
|
||||||
tooltip = tx_hash + "\n%d confirmations"%conf
|
tooltip = tx_hash + "\n%d confirmations"%conf
|
||||||
|
|
||||||
tx = self.wallet.tx_history.get(tx_hash)
|
# tx = self.wallet.tx_history.get(tx_hash)
|
||||||
details = "Transaction Details:\n\n"
|
details = "Transaction Details:\n\n" \
|
||||||
details+= "Transaction ID:\n" + tx_hash + "\n\n"
|
+ "Transaction ID:\n" + tx_hash + "\n\n" \
|
||||||
details+= "Status: %d confirmations\n\n"%conf
|
+ "Status: %d confirmations\n\n"%conf \
|
||||||
details+= "Date: %s\n\n"%time_str
|
+ "Date: %s\n\n"%time_str \
|
||||||
details+= "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n"
|
+ "Inputs:\n-"+ '\n-'.join(tx['inputs']) + "\n\n" \
|
||||||
details+= "Outputs:\n-"+ '\n-'.join(tx['outputs'])
|
+ "Outputs:\n-"+ '\n-'.join(tx['outputs'])
|
||||||
|
r = self.wallet.receipts.get(tx_hash)
|
||||||
|
if r:
|
||||||
|
details += "\n_______________________________________" \
|
||||||
|
+ '\n\nSigned URI: ' + r[2] \
|
||||||
|
+ "\n\nSigned by: " + r[0] \
|
||||||
|
+ '\n\nSignature: ' + r[1]
|
||||||
|
|
||||||
|
|
||||||
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
|
self.history_list.prepend( [tx_hash, conf_icon, time_str, label, is_default_label,
|
||||||
('+' if v>0 else '') + format_satoshis(v), format_satoshis(balance), tooltip, details] )
|
format_satoshis(v,True), format_satoshis(balance), tooltip, details] )
|
||||||
if cursor: self.history_treeview.set_cursor( cursor )
|
if cursor: self.history_treeview.set_cursor( cursor )
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,9 @@
|
|||||||
|
|
||||||
import random, socket, ast
|
import random, socket, ast
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Interface:
|
class Interface:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.servers = ['ecdsa.org','electrum.novit.ro'] # list of default servers
|
self.servers = ['ecdsa.org','electrum.novit.ro'] # list of default servers
|
||||||
|
|||||||
94
client/msqr.py
Normal file
94
client/msqr.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# from http://eli.thegreenplace.net/2009/03/07/computing-modular-square-roots-in-python/
|
||||||
|
|
||||||
|
def modular_sqrt(a, p):
|
||||||
|
""" Find a quadratic residue (mod p) of 'a'. p
|
||||||
|
must be an odd prime.
|
||||||
|
|
||||||
|
Solve the congruence of the form:
|
||||||
|
x^2 = a (mod p)
|
||||||
|
And returns x. Note that p - x is also a root.
|
||||||
|
|
||||||
|
0 is returned is no square root exists for
|
||||||
|
these a and p.
|
||||||
|
|
||||||
|
The Tonelli-Shanks algorithm is used (except
|
||||||
|
for some simple cases in which the solution
|
||||||
|
is known from an identity). This algorithm
|
||||||
|
runs in polynomial time (unless the
|
||||||
|
generalized Riemann hypothesis is false).
|
||||||
|
"""
|
||||||
|
# Simple cases
|
||||||
|
#
|
||||||
|
if legendre_symbol(a, p) != 1:
|
||||||
|
return 0
|
||||||
|
elif a == 0:
|
||||||
|
return 0
|
||||||
|
elif p == 2:
|
||||||
|
return p
|
||||||
|
elif p % 4 == 3:
|
||||||
|
return pow(a, (p + 1) / 4, p)
|
||||||
|
|
||||||
|
# Partition p-1 to s * 2^e for an odd s (i.e.
|
||||||
|
# reduce all the powers of 2 from p-1)
|
||||||
|
#
|
||||||
|
s = p - 1
|
||||||
|
e = 0
|
||||||
|
while s % 2 == 0:
|
||||||
|
s /= 2
|
||||||
|
e += 1
|
||||||
|
|
||||||
|
# Find some 'n' with a legendre symbol n|p = -1.
|
||||||
|
# Shouldn't take long.
|
||||||
|
#
|
||||||
|
n = 2
|
||||||
|
while legendre_symbol(n, p) != -1:
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
# Here be dragons!
|
||||||
|
# Read the paper "Square roots from 1; 24, 51,
|
||||||
|
# 10 to Dan Shanks" by Ezra Brown for more
|
||||||
|
# information
|
||||||
|
#
|
||||||
|
|
||||||
|
# x is a guess of the square root that gets better
|
||||||
|
# with each iteration.
|
||||||
|
# b is the "fudge factor" - by how much we're off
|
||||||
|
# with the guess. The invariant x^2 = ab (mod p)
|
||||||
|
# is maintained throughout the loop.
|
||||||
|
# g is used for successive powers of n to update
|
||||||
|
# both a and b
|
||||||
|
# r is the exponent - decreases with each update
|
||||||
|
#
|
||||||
|
x = pow(a, (s + 1) / 2, p)
|
||||||
|
b = pow(a, s, p)
|
||||||
|
g = pow(n, s, p)
|
||||||
|
r = e
|
||||||
|
|
||||||
|
while True:
|
||||||
|
t = b
|
||||||
|
m = 0
|
||||||
|
for m in xrange(r):
|
||||||
|
if t == 1:
|
||||||
|
break
|
||||||
|
t = pow(t, 2, p)
|
||||||
|
|
||||||
|
if m == 0:
|
||||||
|
return x
|
||||||
|
|
||||||
|
gs = pow(g, 2 ** (r - m - 1), p)
|
||||||
|
g = (gs * gs) % p
|
||||||
|
x = (x * gs) % p
|
||||||
|
b = (b * g) % p
|
||||||
|
r = m
|
||||||
|
|
||||||
|
def legendre_symbol(a, p):
|
||||||
|
""" Compute the Legendre symbol a|p using
|
||||||
|
Euler's criterion. p is a prime, a is
|
||||||
|
relatively prime to p (if p divides
|
||||||
|
a, then a|p = 0)
|
||||||
|
|
||||||
|
Returns 1 if a has a square root modulo
|
||||||
|
p, -1 otherwise.
|
||||||
|
"""
|
||||||
|
ls = pow(a, (p - 1) / 2, p)
|
||||||
|
return -1 if ls == p - 1 else ls
|
||||||
@ -1,2 +1,2 @@
|
|||||||
ELECTRUM_VERSION = "0.37"
|
ELECTRUM_VERSION = "0.38"
|
||||||
SEED_VERSION = 4 # bump this everytime the seed generation is modified
|
SEED_VERSION = 4 # bump this everytime the seed generation is modified
|
||||||
|
|||||||
165
client/wallet.py
165
client/wallet.py
@ -17,8 +17,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
import sys, base64, os, re, hashlib, socket, getpass, copy, operator, ast, random
|
import sys, base64, os, re, hashlib, copy, operator, ast
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ecdsa
|
import ecdsa
|
||||||
@ -151,10 +150,6 @@ def int_to_hex(i, length=1):
|
|||||||
return s.decode('hex')[::-1].encode('hex')
|
return s.decode('hex')[::-1].encode('hex')
|
||||||
|
|
||||||
|
|
||||||
# URL decode
|
|
||||||
_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE)
|
|
||||||
urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
|
|
||||||
|
|
||||||
# AES
|
# AES
|
||||||
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
|
EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
|
||||||
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
|
DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
|
||||||
@ -218,12 +213,22 @@ def raw_tx( inputs, outputs, for_sig = None ):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def format_satoshis(x, is_diff=False):
|
||||||
|
from decimal import Decimal
|
||||||
|
s = str( Decimal(x) /100000000 )
|
||||||
|
if is_diff and x>0:
|
||||||
|
s = "+" + s
|
||||||
|
if not '.' in s: s += '.'
|
||||||
|
p = s.find('.')
|
||||||
|
s += " "*( 9 - ( len(s) - p ))
|
||||||
|
s = " "*( 5 - ( p )) + s
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
from version import ELECTRUM_VERSION, SEED_VERSION
|
from version import ELECTRUM_VERSION, SEED_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Wallet:
|
class Wallet:
|
||||||
def __init__(self, interface):
|
def __init__(self, interface):
|
||||||
|
|
||||||
@ -242,6 +247,11 @@ class Wallet:
|
|||||||
self.status = {} # current status of addresses
|
self.status = {} # current status of addresses
|
||||||
self.history = {}
|
self.history = {}
|
||||||
self.labels = {} # labels for addresses and transactions
|
self.labels = {} # labels for addresses and transactions
|
||||||
|
self.aliases = {} # aliases for addresses
|
||||||
|
self.authorities = {} # trusted addresses
|
||||||
|
|
||||||
|
self.receipts = {} # signed URIs
|
||||||
|
self.receipt = None # next receipt
|
||||||
self.addressbook = [] # outgoing addresses, for payments
|
self.addressbook = [] # outgoing addresses, for payments
|
||||||
|
|
||||||
# not saved
|
# not saved
|
||||||
@ -287,7 +297,7 @@ class Wallet:
|
|||||||
seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
|
seed = "%032x"%ecdsa.util.randrange( pow(2,128) )
|
||||||
self.init_mpk(seed)
|
self.init_mpk(seed)
|
||||||
# encrypt
|
# encrypt
|
||||||
self.seed = wallet.pw_encode( seed, password )
|
self.seed = self.pw_encode( seed, password )
|
||||||
|
|
||||||
def init_mpk(self,seed):
|
def init_mpk(self,seed):
|
||||||
# public key
|
# public key
|
||||||
@ -323,7 +333,7 @@ class Wallet:
|
|||||||
def get_sequence(self,n,for_change):
|
def get_sequence(self,n,for_change):
|
||||||
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) )
|
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key ) )
|
||||||
|
|
||||||
def get_private_key2(self, address, password):
|
def get_private_key(self, address, password):
|
||||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||||
order = generator_secp256k1.order()
|
order = generator_secp256k1.order()
|
||||||
|
|
||||||
@ -346,10 +356,65 @@ class Wallet:
|
|||||||
|
|
||||||
pk = number_to_string(secexp,order)
|
pk = number_to_string(secexp,order)
|
||||||
return pk
|
return pk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_new_address2(self, for_change):
|
def msg_magic(self, message):
|
||||||
|
return "\x18Bitcoin Signed Message:\n" + chr( len(message) ) + message
|
||||||
|
|
||||||
|
def sign_message(self, address, message, password):
|
||||||
|
private_key = ecdsa.SigningKey.from_string( self.get_private_key(address, password), curve = SECP256k1 )
|
||||||
|
public_key = private_key.get_verifying_key()
|
||||||
|
signature = private_key.sign_digest( Hash( self.msg_magic( message ) ), sigencode = ecdsa.util.sigencode_string )
|
||||||
|
assert public_key.verify_digest( signature, Hash( self.msg_magic( message ) ), sigdecode = ecdsa.util.sigdecode_string)
|
||||||
|
for i in range(4):
|
||||||
|
sig = base64.b64encode( chr(27+i) + signature )
|
||||||
|
try:
|
||||||
|
self.verify_message( address, sig, message)
|
||||||
|
return sig
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise BaseException("error: cannot sign message")
|
||||||
|
|
||||||
|
|
||||||
|
def verify_message(self, address, signature, message):
|
||||||
|
""" See http://www.secg.org/download/aid-780/sec1-v2.pdf for the math """
|
||||||
|
from ecdsa import numbertheory, ellipticcurve, util
|
||||||
|
import msqr
|
||||||
|
curve = curve_secp256k1
|
||||||
|
G = generator_secp256k1
|
||||||
|
order = G.order()
|
||||||
|
# extract r,s from signature
|
||||||
|
sig = base64.b64decode(signature)
|
||||||
|
if len(sig) != 65: raise BaseException("Wrong encoding")
|
||||||
|
r,s = util.sigdecode_string(sig[1:], order)
|
||||||
|
recid = ord(sig[0]) - 27
|
||||||
|
# 1.1
|
||||||
|
x = r + (recid/2) * order
|
||||||
|
# 1.3
|
||||||
|
alpha = ( x * x * x + curve.a() * x + curve.b() ) % curve.p()
|
||||||
|
beta = msqr.modular_sqrt(alpha, curve.p())
|
||||||
|
y = beta if (beta - recid) % 2 == 0 else curve.p() - beta
|
||||||
|
# 1.4 the constructor checks that nR is at infinity
|
||||||
|
R = ellipticcurve.Point(curve, x, y, order)
|
||||||
|
# 1.5 compute e from message:
|
||||||
|
h = Hash( self.msg_magic( message ) )
|
||||||
|
e = string_to_number(h)
|
||||||
|
minus_e = -e % order
|
||||||
|
# 1.6 compute Q = r^-1 (sR - eG)
|
||||||
|
inv_r = numbertheory.inverse_mod(r,order)
|
||||||
|
Q = inv_r * ( s * R + minus_e * G )
|
||||||
|
public_key = ecdsa.VerifyingKey.from_public_point( Q, curve = SECP256k1 )
|
||||||
|
# check that Q is the public key
|
||||||
|
public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string)
|
||||||
|
# check that we get the original signing address
|
||||||
|
addr = public_key_to_bc_address( '04'.decode('hex') + public_key.to_string() )
|
||||||
|
# print addr
|
||||||
|
if address != addr:
|
||||||
|
print "bad signature"
|
||||||
|
raise BaseException("Bad signature")
|
||||||
|
|
||||||
|
|
||||||
|
def create_new_address(self, for_change):
|
||||||
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """
|
""" Publickey(type,n) = Master_public_key + H(n|S|type)*point """
|
||||||
curve = SECP256k1
|
curve = SECP256k1
|
||||||
n = len(self.change_addresses) if for_change else len(self.addresses)
|
n = len(self.change_addresses) if for_change else len(self.addresses)
|
||||||
@ -374,12 +439,12 @@ class Wallet:
|
|||||||
is_new = False
|
is_new = False
|
||||||
while True:
|
while True:
|
||||||
if self.change_addresses == []:
|
if self.change_addresses == []:
|
||||||
self.create_new_address2(True)
|
self.create_new_address(True)
|
||||||
is_new = True
|
is_new = True
|
||||||
continue
|
continue
|
||||||
a = self.change_addresses[-1]
|
a = self.change_addresses[-1]
|
||||||
if self.history.get(a):
|
if self.history.get(a):
|
||||||
self.create_new_address2(True)
|
self.create_new_address(True)
|
||||||
is_new = True
|
is_new = True
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
@ -387,13 +452,13 @@ class Wallet:
|
|||||||
n = self.gap_limit
|
n = self.gap_limit
|
||||||
while True:
|
while True:
|
||||||
if len(self.addresses) < n:
|
if len(self.addresses) < n:
|
||||||
self.create_new_address2(False)
|
self.create_new_address(False)
|
||||||
is_new = True
|
is_new = True
|
||||||
continue
|
continue
|
||||||
if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
|
if map( lambda a: self.history.get(a), self.addresses[-n:] ) == n*[[]]:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.create_new_address2(False)
|
self.create_new_address(False)
|
||||||
is_new = True
|
is_new = True
|
||||||
|
|
||||||
|
|
||||||
@ -427,6 +492,9 @@ class Wallet:
|
|||||||
'labels':self.labels,
|
'labels':self.labels,
|
||||||
'contacts':self.addressbook,
|
'contacts':self.addressbook,
|
||||||
'imported_keys':self.imported_keys,
|
'imported_keys':self.imported_keys,
|
||||||
|
'aliases':self.aliases,
|
||||||
|
'authorities':self.authorities,
|
||||||
|
'receipts':self.receipts,
|
||||||
}
|
}
|
||||||
f = open(self.path,"w")
|
f = open(self.path,"w")
|
||||||
f.write( repr(s) )
|
f.write( repr(s) )
|
||||||
@ -457,6 +525,9 @@ class Wallet:
|
|||||||
self.labels = d.get('labels')
|
self.labels = d.get('labels')
|
||||||
self.addressbook = d.get('contacts')
|
self.addressbook = d.get('contacts')
|
||||||
self.imported_keys = d.get('imported_keys',{})
|
self.imported_keys = d.get('imported_keys',{})
|
||||||
|
self.aliases = d.get('aliases',{})
|
||||||
|
self.authorities = d.get('authorities',{})
|
||||||
|
self.receipts = d.get('receipts',{})
|
||||||
except:
|
except:
|
||||||
raise BaseException(upgrade_msg)
|
raise BaseException(upgrade_msg)
|
||||||
|
|
||||||
@ -473,7 +544,7 @@ class Wallet:
|
|||||||
if not self.history.get(addr):
|
if not self.history.get(addr):
|
||||||
n = n + 1
|
n = n + 1
|
||||||
if n < self.gap_limit:
|
if n < self.gap_limit:
|
||||||
new_address = self.create_new_address2(False)
|
new_address = self.create_new_address(False)
|
||||||
self.history[new_address] = [] #get from server
|
self.history[new_address] = [] #get from server
|
||||||
return True, new_address
|
return True, new_address
|
||||||
else:
|
else:
|
||||||
@ -559,7 +630,7 @@ class Wallet:
|
|||||||
s_inputs = []
|
s_inputs = []
|
||||||
for i in range(len(inputs)):
|
for i in range(len(inputs)):
|
||||||
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
|
addr, v, p_hash, p_pos, p_scriptPubKey, _, _ = inputs[i]
|
||||||
private_key = ecdsa.SigningKey.from_string( self.get_private_key2(addr, password), curve = SECP256k1 )
|
private_key = ecdsa.SigningKey.from_string( self.get_private_key(addr, password), curve = SECP256k1 )
|
||||||
public_key = private_key.get_verifying_key()
|
public_key = private_key.get_verifying_key()
|
||||||
pubkey = public_key.to_string()
|
pubkey = public_key.to_string()
|
||||||
tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
|
tx = filter( raw_tx( inputs, outputs, for_sig = i ) )
|
||||||
@ -634,19 +705,19 @@ class Wallet:
|
|||||||
def mktx(self, to_address, amount, label, password, fee=None):
|
def mktx(self, to_address, amount, label, password, fee=None):
|
||||||
if not self.is_valid(to_address):
|
if not self.is_valid(to_address):
|
||||||
raise BaseException("Invalid address")
|
raise BaseException("Invalid address")
|
||||||
inputs, total, fee = wallet.choose_tx_inputs( amount, fee )
|
inputs, total, fee = self.choose_tx_inputs( amount, fee )
|
||||||
if not inputs:
|
if not inputs:
|
||||||
raise BaseException("Not enough funds")
|
raise BaseException("Not enough funds")
|
||||||
outputs = wallet.choose_tx_outputs( to_address, amount, fee, total )
|
outputs = self.choose_tx_outputs( to_address, amount, fee, total )
|
||||||
s_inputs = wallet.sign_inputs( inputs, outputs, password )
|
s_inputs = self.sign_inputs( inputs, outputs, password )
|
||||||
|
|
||||||
tx = filter( raw_tx( s_inputs, outputs ) )
|
tx = filter( raw_tx( s_inputs, outputs ) )
|
||||||
if to_address not in self.addressbook:
|
if to_address not in self.addressbook:
|
||||||
self.addressbook.append(to_address)
|
self.addressbook.append(to_address)
|
||||||
if label:
|
if label:
|
||||||
tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
|
tx_hash = Hash(tx.decode('hex') )[::-1].encode('hex')
|
||||||
wallet.labels[tx_hash] = label
|
self.labels[tx_hash] = label
|
||||||
wallet.save()
|
self.save()
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
def sendtx(self, tx):
|
def sendtx(self, tx):
|
||||||
@ -654,5 +725,51 @@ class Wallet:
|
|||||||
out = self.interface.send_tx(tx)
|
out = self.interface.send_tx(tx)
|
||||||
if out != tx_hash:
|
if out != tx_hash:
|
||||||
return False, "error: " + out
|
return False, "error: " + out
|
||||||
|
if self.receipt:
|
||||||
|
self.receipts[tx_hash] = self.receipt
|
||||||
|
self.receipt = None
|
||||||
return True, out
|
return True, out
|
||||||
|
|
||||||
|
|
||||||
|
def read_alias(self, alias):
|
||||||
|
# this might not be the right place for this function.
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
m1 = re.match('([\w\-\.]+)@((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||||
|
m2 = re.match('((\w[\w\-]+\.)+[\w\-]+)', alias)
|
||||||
|
if m1:
|
||||||
|
url = 'http://' + m1.group(2) + '/bitcoin.id/' + m1.group(1)
|
||||||
|
elif m2:
|
||||||
|
url = 'http://' + alias + '/bitcoin.id'
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
try:
|
||||||
|
lines = urllib.urlopen(url).readlines()
|
||||||
|
except:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
# line 0
|
||||||
|
line = lines[0].strip().split(':')
|
||||||
|
if len(line) == 1:
|
||||||
|
auth_name = None
|
||||||
|
target = signing_addr = line[0]
|
||||||
|
else:
|
||||||
|
target, auth_name, signing_addr, signature = line
|
||||||
|
msg = "alias:%s:%s:%s"%(alias,target,auth_name)
|
||||||
|
print msg, signature
|
||||||
|
self.verify_message(signing_addr, signature, msg)
|
||||||
|
|
||||||
|
# other lines are signed updates
|
||||||
|
for line in lines[1:]:
|
||||||
|
line = line.strip()
|
||||||
|
if not line: continue
|
||||||
|
line = line.split(':')
|
||||||
|
previous = target
|
||||||
|
print repr(line)
|
||||||
|
target, signature = line
|
||||||
|
self.verify_message(previous, signature, "alias:%s:%s"%(alias,target))
|
||||||
|
|
||||||
|
if not self.is_valid(target):
|
||||||
|
raise BaseException("Invalid bitcoin address")
|
||||||
|
|
||||||
|
return target, signing_addr, auth_name
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user